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

feat: improved permission handling for MapFormField #1228

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 58 additions & 39 deletions app/src/gui/fields/maps/MapFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Implement MapFormField for entry of data via maps in FAIMS
*/

import React, {useEffect, useState} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import './MapFormField.css';
import MapWrapper from './MapWrapper';

Expand All @@ -29,6 +29,12 @@ import {FieldProps} from 'formik';
import {Alert} from '@mui/material';
import {Capacitor} from '@capacitor/core';
import {APP_NAME} from '../../../buildconfig';
import {useNotification} from '../../../context/popup';

// If no center is available - pass this through
// Sydney CBD
const FALLBACK_CENTER = [151.2093, -33.8688];

export interface MapFieldProps extends FieldProps {
label?: string;
featureType: 'Point' | 'Polygon' | 'LineString';
Expand All @@ -44,52 +50,66 @@ export function MapFormField({
form,
...props
}: MapFieldProps): JSX.Element {
// get previous form state if available
let initialFeatures = {};
if (form.values[field.name]) {
initialFeatures = form.values[field.name];
}
// State

// center location of map - use provided center if any
const [center, setCenter] = useState<number[] | undefined>(props.center);

// and a ref to track if gps location has already been requested
const gpsCenterRequested = useRef<boolean>(false);

// flag set if we find we don't have location permission
const [noPermission, setNoPermission] = useState(false);

const [drawnFeatures, setDrawnFeatures] =
useState<GeoJSONFeatureCollection>(initialFeatures);

// default props.center if not defined
if (!props.center) {
props.center = [0, 0];
}
const [center, setCenter] = useState(props.center);
// Use form value as default field features - otherwise empty {}
const [drawnFeatures, setDrawnFeatures] = useState<GeoJSONFeatureCollection>(
form.values[field.name] ?? {}
);

if (!props.zoom) {
props.zoom = 14;
}
// Default zoom level
const zoom = props.zoom ?? 14;

// default to point if not specified
if (!props.featureType) {
props.featureType = 'Point';
}
const featureType = props.featureType ?? 'Point';

if (!props.label) {
props.label = `Get ${props.featureType}`;
}
// default label
const label = props.label ?? `Get ${props.featureType}`;

const mapCallback = (theFeatures: GeoJSONFeatureCollection) => {
setDrawnFeatures(theFeatures);
form.setFieldValue(field.name, theFeatures, true);
};

// get the current GPS location if don't know the map center
if (center[0] === 0 && center[1] === 0) {
Geolocation.getCurrentPosition()
.then(result => {
setCenter([result.coords.longitude, result.coords.latitude]);
})
.catch(() => {
setNoPermission(true);
});
}
// notification manager
const notify = useNotification();

useEffect(() => {
const getCoords = async () => {
// Always get current position on component mount - this forces a permission
// request
if (!gpsCenterRequested.current) {
// Mark that we've requested already
gpsCenterRequested.current = true;
Geolocation.getCurrentPosition()
.then(result => {
// Only store the center result if we actually need it
if (center === undefined) {
setCenter([result.coords.longitude, result.coords.latitude]);
}
// Since we use a ref to track this running, we can safely run a state
// update here without infinite loop
setNoPermission(false);
})
.catch(() => {
notify.showWarning(
'We were unable to access your current location. Map fields may not work as expected.'
);
setNoPermission(true);
});
}
};
getCoords();
}, []);
PeterBaker0 marked this conversation as resolved.
Show resolved Hide resolved

let valueText = '';
if (drawnFeatures.features && drawnFeatures.features.length > 0) {
Expand All @@ -114,13 +134,12 @@ export function MapFormField({
<div>
<div>
<MapWrapper
label={
props.label ? props.label : 'Get ' + props.featureType + ' from Map'
}
featureType={props.featureType}
label={label}
PeterBaker0 marked this conversation as resolved.
Show resolved Hide resolved
featureType={featureType}
features={drawnFeatures}
zoom={props.zoom}
center={center}
zoom={zoom}
center={center ?? FALLBACK_CENTER}
fallbackCenter={center === undefined}
callbackFn={mapCallback}
geoTiff={props.geoTiff}
projection={props.projection}
Expand Down
19 changes: 11 additions & 8 deletions app/src/gui/fields/maps/MapWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ interface MapProps extends ButtonProps {
featureType: 'Point' | 'Polygon' | 'LineString';
zoom: number;
center: Array<number>;
fallbackCenter: boolean;
callbackFn: (features: object) => void;
setNoPermission: (flag: boolean) => void;
}
Expand All @@ -67,6 +68,7 @@ import {AppBar, Dialog, IconButton, Toolbar, Typography} from '@mui/material';
import Feature from 'ol/Feature';
import {Geometry} from 'ol/geom';
import {createCenterControl} from '../../components/map/center-control';
import {useNotification} from '../../../context/popup';

const styles = {
mapContainer: {
Expand All @@ -86,6 +88,9 @@ function MapWrapper(props: MapProps) {
const defaultMapProjection = 'EPSG:3857';
const geoJson = new GeoJSON();

// notifications
const notify = useNotification();

// create state ref that can be accessed in OpenLayers onclick callback function
// https://stackoverflow.com/a/60643670
const mapRef = useRef<Map | undefined>();
Expand Down Expand Up @@ -232,15 +237,13 @@ function MapWrapper(props: MapProps) {
};

const handleClickOpen = () => {
// only show the map if we have a center
if (props.center[0] !== 0 && props.center[1] !== 0) {
setMapOpen(true);
// reset this in case we set it before
props.setNoPermission(false);
} else {
props.setNoPermission(true);
if (props.fallbackCenter) {
notify.showWarning(
'Using default map location - unable to determine current location and no center location configured.'
);
}
// TODO: should do something to inform the user here...
// We always provide a center, so it's always safe to open the map
setMapOpen(true);
};

const refCallback = (element: HTMLElement | null) => {
Expand Down
Loading