Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Live location sharing - consolidate maps (#8236)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Kerry committed Apr 11, 2022
1 parent 944e11d commit 9ba55d1
Show file tree
Hide file tree
Showing 16 changed files with 889 additions and 234 deletions.
2 changes: 2 additions & 0 deletions __mocks__/maplibre-gl.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class MockMap extends EventEmitter {
removeControl = jest.fn();
zoomIn = jest.fn();
zoomOut = jest.fn();
setCenter = jest.fn();
setStyle = jest.fn();
}
const MockMapInstance = new MockMap();

Expand Down
49 changes: 5 additions & 44 deletions res/css/views/dialogs/_LocationViewDialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,49 +48,10 @@ limitations under the License.
background-color: $dialog-close-external-color;
}
}
}

.mx_MLocationBody {
position: absolute;

.mx_MLocationBody_map {
width: 80vw;
height: 80vh;
}

.mx_MLocationBody_zoomButtons {
position: absolute;
display: grid;
grid-template-columns: auto;
grid-row-gap: 8px;

right: 24px;
bottom: 48px;

.mx_AccessibleButton {
background-color: $background;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.25);
border-radius: 4px;
width: 24px;
height: 24px;

.mx_MLocationBody_zoomButton {
background-color: $primary-content;
margin: 4px;
width: 16px;
height: 16px;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}

.mx_MLocationBody_plusButton {
mask-image: url('$(res)/img/element-icons/plus-button.svg');
}

.mx_MLocationBody_minusButton {
mask-image: url('$(res)/img/element-icons/minus-button.svg');
}
}
}
}
.mx_LocationViewDialog_map {
width: 80vw;
height: 80vh;
border-radius: 8px;
}
1 change: 1 addition & 0 deletions src/components/views/location/LocationPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
return (
<div className="mx_LocationPicker">
<div id="mx_LocationPicker_map" />

{ this.props.shareType === LocationShareType.Pin && <div className="mx_LocationPicker_pinText">
<span>
{ this.state.position ? _t("Click to move the pin") : _t("Click to drop a pin") }
Expand Down
87 changes: 32 additions & 55 deletions src/components/views/location/LocationViewDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ limitations under the License.

import React from 'react';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { ClientEvent, IClientWellKnown, MatrixClient } from 'matrix-js-sdk/src/client';
import { MatrixClient } from 'matrix-js-sdk/src/client';

import BaseDialog from "../dialogs/BaseDialog";
import { IDialogProps } from "../dialogs/IDialogProps";
import { LocationBodyContent } from '../messages/MLocationBody';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
import { parseGeoUri, locationEventGeoUri, createMapWithCoords } from '../../../utils/location';
import { locationEventGeoUri, isSelfLocation } from '../../../utils/location';
import Map from './Map';
import SmartMarker from './SmartMarker';
import ZoomButtons from './ZoomButtons';

interface IProps extends IDialogProps {
matrixClient: MatrixClient;
Expand All @@ -34,78 +35,54 @@ interface IState {
}

export default class LocationViewDialog extends React.Component<IProps, IState> {
private coords: GeolocationCoordinates;
private map?: maplibregl.Map;

constructor(props: IProps) {
super(props);

this.coords = parseGeoUri(locationEventGeoUri(this.props.mxEvent));
this.map = null;
this.state = {
error: undefined,
};
}

componentDidMount() {
if (this.state.error) {
return;
}

this.props.matrixClient.on(ClientEvent.ClientWellKnown, this.updateStyleUrl);

this.map = createMapWithCoords(
this.coords,
true,
this.getBodyId(),
this.getMarkerId(),
(e: Error) => this.setState({ error: e }),
);
}

componentWillUnmount() {
this.props.matrixClient.off(ClientEvent.ClientWellKnown, this.updateStyleUrl);
}

private updateStyleUrl = (clientWellKnown: IClientWellKnown) => {
const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"];
if (style) {
this.map?.setStyle(style);
}
};

private getBodyId = () => {
return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`;
};

private getMarkerId = () => {
return `mx_MLocationViewDialog_marker_${this.props.mxEvent.getId()}`;
};

private onZoomIn = () => {
this.map?.zoomIn();
};

private onZoomOut = () => {
this.map?.zoomOut();
private onError = (error) => {
this.setState({ error });
};

render() {
const { mxEvent } = this.props;

// only pass member to marker when should render avatar marker
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
const geoUri = locationEventGeoUri(mxEvent);
return (
<BaseDialog
className='mx_LocationViewDialog'
onFinished={this.props.onFinished}
fixedWidth={false}
>
<LocationBodyContent
mxEvent={this.props.mxEvent}
bodyId={this.getBodyId()}
markerId={this.getMarkerId()}
error={this.state.error}
zoomButtons={true}
onZoomIn={this.onZoomIn}
onZoomOut={this.onZoomOut}
/>
<Map
id={this.getBodyId()}
centerGeoUri={geoUri}
onError={this.onError}
interactive
className="mx_LocationViewDialog_map"
>
{
({ map }) =>
<>
<SmartMarker
map={map}
id={`${this.getBodyId()}-marker`}
geoUri={geoUri}
roomMember={markerRoomMember}
/>
<ZoomButtons map={map} />
</>
}
</Map>
</BaseDialog>
);
}
Expand Down
101 changes: 101 additions & 0 deletions src/components/views/location/Map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { ReactNode, useContext, useEffect } from 'react';
import classNames from 'classnames';
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';

import MatrixClientContext from '../../../contexts/MatrixClientContext';
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
import { parseGeoUri } from '../../../utils/location';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
import { useMap } from '../../../utils/location/useMap';

const useMapWithStyle = ({ id, centerGeoUri, onError, interactive }) => {
const bodyId = `mx_Map_${id}`;

// style config
const context = useContext(MatrixClientContext);
const mapStyleUrl = useEventEmitterState(
context,
ClientEvent.ClientWellKnown,
(clientWellKnown: IClientWellKnown) => tileServerFromWellKnown(clientWellKnown)?.["map_style_url"],
);

const map = useMap({ interactive, bodyId, onError });

useEffect(() => {
if (mapStyleUrl && map) {
map.setStyle(mapStyleUrl);
}
}, [mapStyleUrl, map]);

useEffect(() => {
if (map && centerGeoUri) {
try {
const coords = parseGeoUri(centerGeoUri);
map.setCenter({ lon: coords.longitude, lat: coords.latitude });
} catch (error) {
logger.error('Could not set map center', centerGeoUri);
}
}
}, [map, centerGeoUri]);

return {
map,
bodyId,
};
};

interface MapProps {
id: string;
interactive?: boolean;
centerGeoUri?: string;
className?: string;
onClick?: () => void;
onError?: (error: Error) => void;
children?: (renderProps: {
map: maplibregl.Map;
}) => ReactNode;
}

const Map: React.FC<MapProps> = ({
centerGeoUri, className, id, onError, onClick, children, interactive,
}) => {
const { map, bodyId } = useMapWithStyle({ centerGeoUri, onError, id, interactive });

const onMapClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
// Eat click events when clicking the attribution button
const target = event.target as Element;
if (target.classList.contains("maplibregl-ctrl-attrib-button")) {
return;
}

onClick && onClick();
};

return <div className={classNames('mx_Map', className)}
id={bodyId}
onClick={onMapClick}
>
{ !!children && !!map && children({ map }) }
</div>;
};

export default Map;
Loading

0 comments on commit 9ba55d1

Please sign in to comment.