Live location sharing: set map bounds to include all locations (#8324)
* open a dialog with map centered around first beacon Signed-off-by: Kerry Archibald <kerrya@element.io> * test dialog opening from beacon body Signed-off-by: Kerry Archibald <kerrya@element.io> * test beaconmarker Signed-off-by: Kerry Archibald <kerrya@element.io> * add bounds to Map comp Signed-off-by: Kerry Archibald <kerrya@element.io> * add focusBeacon to beaconviewdialog, use bounds Signed-off-by: Kerry Archibald <kerrya@element.io> * lint Signed-off-by: Kerry Archibald <kerrya@element.io> * use membercolor on beacon view markers Signed-off-by: Kerry Archibald <kerrya@element.io> * add lnglatbounds to maplibre mock Signed-off-by: Kerry Archibald <kerrya@element.io> * update snapshots for expanded maplibre Map mock Signed-off-by: Kerry Archibald <kerrya@element.io> * test map bounds Signed-off-by: Kerry Archibald <kerrya@element.io> * tidy copy paste comment Signed-off-by: Kerry Archibald <kerrya@element.io> * add fallback when no more live locations Signed-off-by: Kerry Archibald <kerrya@element.io> * accurate signature for getBoundsCenter Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
6b13988eaa
commit
f70186ea9b
16 changed files with 246 additions and 21 deletions
|
@ -58,6 +58,7 @@ const BeaconMarker: React.FC<Props> = ({ map, beacon }) => {
|
|||
id={beacon.identifier}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
useMemberColor
|
||||
/>;
|
||||
};
|
||||
|
||||
|
|
|
@ -29,29 +29,43 @@ import { IDialogProps } from "../dialogs/IDialogProps";
|
|||
import Map from '../location/Map';
|
||||
import ZoomButtons from '../location/ZoomButtons';
|
||||
import BeaconMarker from './BeaconMarker';
|
||||
import { Bounds, getBeaconBounds } from '../../../utils/beacon/bounds';
|
||||
import { getGeoUri } from '../../../utils/beacon';
|
||||
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
roomId: Room['roomId'];
|
||||
matrixClient: MatrixClient;
|
||||
// open the map centered on this beacon's location
|
||||
focusBeacon?: Beacon;
|
||||
}
|
||||
|
||||
// TODO actual center is coming soon
|
||||
// for now just center around first beacon in list
|
||||
const getMapCenterUri = (beacons: Beacon[]): string => {
|
||||
const firstBeaconWithLocation = beacons.find(beacon => beacon.latestLocationState);
|
||||
|
||||
return firstBeaconWithLocation?.latestLocationState?.uri;
|
||||
const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
||||
if (!bounds) {
|
||||
return;
|
||||
}
|
||||
return getGeoUri({
|
||||
latitude: (bounds.north + bounds.south) / 2,
|
||||
longitude: (bounds.east + bounds.west) / 2,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog to view live beacons maximised
|
||||
*/
|
||||
const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }) => {
|
||||
const BeaconViewDialog: React.FC<IProps> = ({
|
||||
focusBeacon,
|
||||
roomId,
|
||||
matrixClient,
|
||||
onFinished,
|
||||
}) => {
|
||||
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
||||
|
||||
const mapCenterUri = getMapCenterUri(liveBeacons);
|
||||
// TODO probably show loader or placeholder when there is no location
|
||||
// to center the map on
|
||||
const bounds = getBeaconBounds(liveBeacons);
|
||||
const centerGeoUri = focusBeacon?.latestLocationState?.uri || getBoundsCenter(bounds);
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
|
@ -60,9 +74,10 @@ const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }
|
|||
fixedWidth={false}
|
||||
>
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
<Map
|
||||
{ !!bounds ? <Map
|
||||
id='mx_BeaconViewDialog'
|
||||
centerGeoUri={mapCenterUri}
|
||||
bounds={bounds}
|
||||
centerGeoUri={centerGeoUri}
|
||||
interactive
|
||||
className="mx_BeaconViewDialog_map"
|
||||
>
|
||||
|
@ -77,7 +92,22 @@ const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }
|
|||
<ZoomButtons map={map} />
|
||||
</>
|
||||
}
|
||||
</Map>
|
||||
</Map> :
|
||||
<div
|
||||
data-test-id='beacon-view-dialog-map-fallback'
|
||||
className='mx_BeaconViewDialog_map mx_BeaconViewDialog_mapFallback'
|
||||
>
|
||||
<LocationIcon className='mx_BeaconViewDialog_mapFallbackIcon' />
|
||||
<span className='mx_BeaconViewDialog_mapFallbackMessage'>{ _t('No live locations') }</span>
|
||||
<AccessibleButton
|
||||
kind='primary'
|
||||
onClick={onFinished}
|
||||
data-test-id='beacon-view-dialog-fallback-close'
|
||||
>
|
||||
{ _t('Close') }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
}
|
||||
</MatrixClientContext.Provider>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React, { ReactNode, useContext, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/matrix';
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
|
||||
|
@ -24,8 +25,9 @@ import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
|||
import { parseGeoUri } from '../../../utils/location';
|
||||
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
|
||||
import { useMap } from '../../../utils/location/useMap';
|
||||
import { Bounds } from '../../../utils/beacon/bounds';
|
||||
|
||||
const useMapWithStyle = ({ id, centerGeoUri, onError, interactive }) => {
|
||||
const useMapWithStyle = ({ id, centerGeoUri, onError, interactive, bounds }) => {
|
||||
const bodyId = `mx_Map_${id}`;
|
||||
|
||||
// style config
|
||||
|
@ -55,6 +57,20 @@ const useMapWithStyle = ({ id, centerGeoUri, onError, interactive }) => {
|
|||
}
|
||||
}, [map, centerGeoUri]);
|
||||
|
||||
useEffect(() => {
|
||||
if (map && bounds) {
|
||||
try {
|
||||
const lngLatBounds = new maplibregl.LngLatBounds(
|
||||
[bounds.west, bounds.south],
|
||||
[bounds.east, bounds.north],
|
||||
);
|
||||
map.fitBounds(lngLatBounds, { padding: 100 });
|
||||
} catch (error) {
|
||||
logger.error('Invalid map bounds', error);
|
||||
}
|
||||
}
|
||||
}, [map, bounds]);
|
||||
|
||||
return {
|
||||
map,
|
||||
bodyId,
|
||||
|
@ -65,6 +81,7 @@ interface MapProps {
|
|||
id: string;
|
||||
interactive?: boolean;
|
||||
centerGeoUri?: string;
|
||||
bounds?: Bounds;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
onError?: (error: Error) => void;
|
||||
|
@ -74,9 +91,15 @@ interface MapProps {
|
|||
}
|
||||
|
||||
const Map: React.FC<MapProps> = ({
|
||||
centerGeoUri, className, id, onError, onClick, children, interactive,
|
||||
bounds,
|
||||
centerGeoUri,
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
interactive,
|
||||
onError, onClick,
|
||||
}) => {
|
||||
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive });
|
||||
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive, bounds });
|
||||
|
||||
const onMapClick = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
|
|
|
@ -32,8 +32,8 @@ import Spinner from '../elements/Spinner';
|
|||
import Map from '../location/Map';
|
||||
import SmartMarker from '../location/SmartMarker';
|
||||
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
|
||||
const useBeaconState = (beaconInfoEvent: MatrixEvent): {
|
||||
beacon?: Beacon;
|
||||
|
@ -105,6 +105,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
|
|||
{
|
||||
roomId: mxEvent.getRoomId(),
|
||||
matrixClient,
|
||||
focusBeacon: beacon,
|
||||
},
|
||||
"mx_BeaconViewDialog_wrapper",
|
||||
false, // isPriority
|
||||
|
|
|
@ -2915,6 +2915,7 @@
|
|||
"Loading live location...": "Loading live location...",
|
||||
"Live location ended": "Live location ended",
|
||||
"Live location error": "Live location error",
|
||||
"No live locations": "No live locations",
|
||||
"An error occured whilst sharing your live location": "An error occured whilst sharing your live location",
|
||||
"You are sharing your live location": "You are sharing your live location",
|
||||
"%(timeRemaining)s left": "%(timeRemaining)s left",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue