From 684b0617aecd5a88a9447a93f34bbed8753799e9 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 6 Dec 2021 10:13:06 +0000 Subject: [PATCH] Support sharing custom locations. (#7185) Add the ability to click on the map to share a specific named location. --- .../views/location/LocationPicker.tsx | 80 ++++++++++++++++--- .../views/messages/MLocationBody.tsx | 14 +++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index 8d4efdfa709..3c22d063452 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -41,7 +41,7 @@ const LocationShareTypeDropdown = ({ onChange, }: IDropdownProps) => { const options = [ - //
{ _t("Share custom location") }
, +
{ _t("Share custom location") }
,
{ _t("Share my current location as a once off") }
, //
{ _t("Share my current location for one minute") }
, //
{ _t("Share my current location for five minutes") }
, @@ -56,7 +56,9 @@ const LocationShareTypeDropdown = ({ return { onChange(LocationShareType[LocationShareType[parseInt(key)]]); }} + onOptionChange={(key: string) => { + onChange(LocationShareType[LocationShareType[parseInt(key)]]); + }} menuWidth={width} label={label} value={value.toString()} @@ -74,13 +76,14 @@ interface IState { description: string; type: LocationShareType; position?: GeolocationPosition; - manual: boolean; + manualPosition?: GeolocationPosition; error: Error; } @replaceableComponent("views.location.LocationPicker") class LocationPicker extends React.Component { private map: maplibregl.Map; + private marker: maplibregl.Marker; private geolocate: maplibregl.GeolocateControl; constructor(props) { @@ -90,7 +93,7 @@ class LocationPicker extends React.Component { description: _t("My location"), type: LocationShareType.OnceOff, position: undefined, - manual: false, + manualPosition: undefined, error: undefined, }; } @@ -113,23 +116,63 @@ class LocationPicker extends React.Component { }); this.map.addControl(this.geolocate); - this.map.on('error', (e)=>{ + this.map.on('error', (e) => { logger.error("Failed to load map: check map_style_url in config.json has a valid URL and API key", e.error); this.setState({ error: e.error }); }); - this.map.on('load', ()=>{ + this.map.on('load', () => { this.geolocate.trigger(); }); + this.map.on('click', (e) => { + this.addMarker(e.lngLat); + this.storeManualPosition(e.lngLat); + this.setState({ type: LocationShareType.Custom }); + }); + this.geolocate.on('geolocate', this.onGeolocate); } + private addMarker(lngLat: maplibregl.LngLat): void { + if (this.marker) return; + this.marker = new maplibregl.Marker({ + draggable: true, + }) + .setLngLat(lngLat) + .addTo(this.map) + .on('dragend', () => { + this.storeManualPosition(this.marker.getLngLat()); + }); + } + + private removeMarker(): void { + if (!this.marker) return; + this.marker.remove(); + this.marker = undefined; + } + + private storeManualPosition(lngLat: maplibregl.LngLat): void { + const manualPosition: GeolocationPosition = { + coords: { + longitude: lngLat.lng, + latitude: lngLat.lat, + altitude: undefined, + accuracy: undefined, + altitudeAccuracy: undefined, + heading: undefined, + speed: undefined, + }, + timestamp: Date.now(), + }; + this.setState({ manualPosition }); + } + componentWillUnmount() { this.geolocate.off('geolocate', this.onGeolocate); } - private onGeolocate = (position) => { + private onGeolocate = (position: GeolocationPosition) => { this.setState({ position }); }; @@ -146,9 +189,12 @@ class LocationPicker extends React.Component { }; private onOk = () => { + const position = (this.state.type == LocationShareType.Custom) ? + this.state.manualPosition : this.state.position; + this.props.onChoose( - this.state.position ? this.getGeoUri(this.state.position) : undefined, - this.state.position ? this.state.position.timestamp : undefined, + position ? this.getGeoUri(position) : undefined, + position ? position.timestamp : undefined, this.state.type, this.state.description, ); @@ -156,6 +202,20 @@ class LocationPicker extends React.Component { }; private onTypeChange= (type: LocationShareType) => { + if (type == LocationShareType.Custom) { + if (!this.state.manualPosition) { + this.setState({ manualPosition: this.state.position }); + } + if (this.state.manualPosition) { + this.addMarker(new maplibregl.LngLat( + this.state.manualPosition?.coords.longitude, + this.state.manualPosition?.coords.latitude, + )); + } + } else { + this.removeMarker(); + } + this.setState({ type }); }; @@ -189,7 +249,7 @@ class LocationPicker extends React.Component { + primaryDisabled={!this.state.position} /> diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index 8e096fa3820..49ff063f40c 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -76,15 +76,22 @@ export default class MLocationBody extends React.Component { componentDidMount() { const config = SdkConfig.get(); + const coordinates = new maplibregl.LngLat(this.coords.longitude, this.coords.latitude); + this.map = new maplibregl.Map({ container: this.getBodyId(), style: config.map_style_url, - center: [this.coords.longitude, this.coords.latitude], + center: coordinates, zoom: 13, }); - new maplibregl.Marker() - .setLngLat([this.coords.longitude, this.coords.latitude]) + new maplibregl.Popup({ + closeButton: false, + closeOnClick: false, + closeOnMove: false, + }) + .setLngLat(coordinates) + .setHTML(this.description) .addTo(this.map); this.map.on('error', (e)=>{ @@ -106,7 +113,6 @@ export default class MLocationBody extends React.Component { return
{ error } - { this.description }
; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2116b8cfa89..1637b0908cd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2103,6 +2103,7 @@ "edited": "edited", "Submit logs": "Submit logs", "Can't load this message": "Can't load this message", + "Share custom location": "Share custom location", "Share my current location as a once off": "Share my current location as a once off", "My location": "My location", "Type of location share": "Type of location share",