Live location sharing - consolidate maps (#8236)

* extract location markers into generic Marker

Signed-off-by: Kerry Archibald <kerrya@element.io>

* wrap marker in smartmarker

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test smartmarker

Signed-off-by: Kerry Archibald <kerrya@element.io>

* working map in location body

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test Map

Signed-off-by: Kerry Archibald <kerrya@element.io>

* remove skinned sdk

Signed-off-by: Kerry Archibald <kerrya@element.io>

* update snaps with new mocks

Signed-off-by: Kerry Archibald <kerrya@element.io>

* use new ZoomButtons in MLocationBody

Signed-off-by: Kerry Archibald <kerrya@element.io>

* make LocationViewDialog map interactive

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test MLocationBody

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test LocationViewDialog

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add copyrights, shrink snapshot

Signed-off-by: Kerry Archibald <kerrya@element.io>

* update comment

Signed-off-by: Kerry Archibald <kerrya@element.io>

* lint

Signed-off-by: Kerry Archibald <kerrya@element.io>

* lint

Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
Kerry 2022-04-11 18:40:06 +02:00 committed by GitHub
parent 944e11d7d6
commit 9ba55d1d14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 890 additions and 235 deletions

View file

@ -15,28 +15,23 @@ limitations under the License.
*/
import React from 'react';
import maplibregl from 'maplibre-gl';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
import { IBodyProps } from "./IBodyProps";
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import {
parseGeoUri,
locationEventGeoUri,
createMapWithCoords,
getLocationShareErrorMessage,
LocationShareError,
isSelfLocation,
} from '../../../utils/location';
import LocationViewDialog from '../location/LocationViewDialog';
import MatrixClientContext from '../../../contexts/MatrixClientContext';
import TooltipTarget from '../elements/TooltipTarget';
import { Alignment } from '../elements/Tooltip';
import AccessibleButton from '../elements/AccessibleButton';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
import MatrixClientContext from '../../../contexts/MatrixClientContext';
import Marker from '../location/Marker';
import LocationViewDialog from '../location/LocationViewDialog';
import Map from '../location/Map';
import SmartMarker from '../location/SmartMarker';
import { IBodyProps } from "./IBodyProps";
interface IState {
error: Error;
@ -45,61 +40,23 @@ interface IState {
export default class MLocationBody extends React.Component<IBodyProps, IState> {
public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
private coords: GeolocationCoordinates;
private bodyId: string;
private markerId: string;
private map?: maplibregl.Map = null;
private mapId: string;
constructor(props: IBodyProps) {
super(props);
const randomString = Math.random().toString(16).slice(2, 10);
// multiple instances of same map might be in document
// eg thread and main timeline, reply
const idSuffix = `${props.mxEvent.getId()}_${randomString}`;
this.bodyId = `mx_MLocationBody_${idSuffix}`;
this.markerId = `mx_MLocationBody_marker_${idSuffix}`;
this.coords = parseGeoUri(locationEventGeoUri(this.props.mxEvent));
this.mapId = `mx_MLocationBody_${idSuffix}`;
this.state = {
error: undefined,
};
}
componentDidMount() {
if (this.state.error) {
return;
}
this.context.on(ClientEvent.ClientWellKnown, this.updateStyleUrl);
this.map = createMapWithCoords(
this.coords,
false,
this.bodyId,
this.markerId,
(e: Error) => this.setState({ error: e }),
);
}
componentWillUnmount() {
this.context.off(ClientEvent.ClientWellKnown, this.updateStyleUrl);
}
private updateStyleUrl = (clientWellKnown: IClientWellKnown) => {
const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"];
if (style) {
this.map?.setStyle(style);
}
};
private onClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
// Don't open map if we clicked the attribution button
const target = event.target as Element;
if (target.classList.contains("maplibregl-ctrl-attrib-button")) {
return;
}
private onClick = () => {
Modal.createTrackedDialog(
'Location View',
'',
@ -114,14 +71,17 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
);
};
private onError = (error) => {
this.setState({ error });
};
render(): React.ReactElement<HTMLDivElement> {
return this.state.error ?
<LocationBodyFallbackContent error={this.state.error} event={this.props.mxEvent} /> :
<LocationBodyContent
mxEvent={this.props.mxEvent}
bodyId={this.bodyId}
markerId={this.markerId}
error={this.state.error}
mapId={this.mapId}
onError={this.onError}
tooltip={_t("Expand map")}
onClick={this.onClick}
/>;
@ -147,68 +107,52 @@ export const LocationBodyFallbackContent: React.FC<{ event: MatrixEvent, error:
interface LocationBodyContentProps {
mxEvent: MatrixEvent;
bodyId: string;
markerId: string;
error: Error;
mapId: string;
tooltip?: string;
onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
zoomButtons?: boolean;
onZoomIn?: () => void;
onZoomOut?: () => void;
onError: (error: Error) => void;
onClick?: () => void;
}
export const LocationBodyContent: React.FC<LocationBodyContentProps> = (props) => {
const mapDiv = <div
id={props.bodyId}
onClick={props.onClick}
className="mx_MLocationBody_map"
/>;
export const LocationBodyContent: React.FC<LocationBodyContentProps> = ({
mxEvent,
mapId,
tooltip,
onError,
onClick,
}) => {
// only pass member to marker when should render avatar marker
const markerRoomMember = isSelfLocation(props.mxEvent.getContent()) ? props.mxEvent.sender : undefined;
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
const geoUri = locationEventGeoUri(mxEvent);
const mapElement = (<Map
id={mapId}
centerGeoUri={geoUri}
onClick={onClick}
onError={onError}
className="mx_MLocationBody_map"
>
{
({ map }) =>
<SmartMarker
map={map}
id={`${mapId}-marker`}
geoUri={geoUri}
roomMember={markerRoomMember}
/>
}
</Map>);
return <div className="mx_MLocationBody">
{
props.tooltip
tooltip
? <TooltipTarget
label={props.tooltip}
label={tooltip}
alignment={Alignment.InnerBottom}
maxParentWidth={450}
>
{ mapDiv }
{ mapElement }
</TooltipTarget>
: mapDiv
}
<Marker id={props.markerId} roomMember={markerRoomMember} />
{
props.zoomButtons
? <ZoomButtons
onZoomIn={props.onZoomIn}
onZoomOut={props.onZoomOut}
/>
: null
: mapElement
}
</div>;
};
interface IZoomButtonsProps {
onZoomIn: () => void;
onZoomOut: () => void;
}
function ZoomButtons(props: IZoomButtonsProps): React.ReactElement<HTMLDivElement> {
return <div className="mx_MLocationBody_zoomButtons">
<AccessibleButton
onClick={props.onZoomIn}
title={_t("Zoom in")}
>
<div className="mx_MLocationBody_zoomButton mx_MLocationBody_plusButton" />
</AccessibleButton>
<AccessibleButton
onClick={props.onZoomOut}
title={_t("Zoom out")}
>
<div className="mx_MLocationBody_zoomButton mx_MLocationBody_minusButton" />
</AccessibleButton>
</div>;
}