enable geolocation behaviour in location picker for live share type (#8068)
* enable geolocation behaviour in location picker for live share type Signed-off-by: Kerry Archibald <kerrya@element.io> * add empty lines Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
9f6c238827
commit
176e49e312
5 changed files with 82 additions and 69 deletions
|
@ -84,9 +84,7 @@ limitations under the License.
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
&.Own {
|
&.Own {
|
||||||
// color is set by user color class
|
border-color: $accent;
|
||||||
// generated from id
|
|
||||||
border-color: currentColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.Live {
|
&.Live {
|
||||||
|
|
|
@ -62,8 +62,8 @@ limitations under the License.
|
||||||
width: 31px;
|
width: 31px;
|
||||||
height: 31px;
|
height: 31px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: $accent;
|
|
||||||
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
|
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
|
||||||
|
background-color: currentColor;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -87,7 +87,7 @@ limitations under the License.
|
||||||
width: 9px;
|
width: 9px;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: $accent;
|
background-color: currentColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,3 +135,11 @@ limitations under the License.
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// live marker color is set by user color class
|
||||||
|
// generated from userid
|
||||||
|
// others are $accent
|
||||||
|
.mx_MLocationBody_marker-Self,
|
||||||
|
.mx_MLocationBody_marker-Pin {
|
||||||
|
color: $accent;
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import maplibregl, { MapMouseEvent } from 'maplibre-gl';
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||||
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
|
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
@ -33,6 +34,7 @@ import { Icon as LocationIcon } from '../../../../res/img/element-icons/location
|
||||||
import { LocationShareError } from './LocationShareErrors';
|
import { LocationShareError } from './LocationShareErrors';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import { MapError } from './MapError';
|
import { MapError } from './MapError';
|
||||||
|
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
|
||||||
export interface ILocationPickerProps {
|
export interface ILocationPickerProps {
|
||||||
sender: RoomMember;
|
sender: RoomMember;
|
||||||
shareType: LocationShareType;
|
shareType: LocationShareType;
|
||||||
|
@ -52,13 +54,8 @@ interface IState {
|
||||||
error?: LocationShareError;
|
error?: LocationShareError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
const isSharingOwnLocation = (shareType: LocationShareType): boolean =>
|
||||||
* An older version of this file allowed manually picking a location on
|
shareType === LocationShareType.Own || shareType === LocationShareType.Live;
|
||||||
* the map to share, instead of sharing your current location.
|
|
||||||
* Since the current designs do not cover this case, it was removed from
|
|
||||||
* the code but you should be able to find it in the git history by
|
|
||||||
* searching for the commit that remove manualPosition from this file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@replaceableComponent("views.location.LocationPicker")
|
@replaceableComponent("views.location.LocationPicker")
|
||||||
class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
||||||
|
@ -117,7 +114,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
||||||
|
|
||||||
this.geolocate.on('error', this.onGeolocateError);
|
this.geolocate.on('error', this.onGeolocateError);
|
||||||
|
|
||||||
if (this.props.shareType === LocationShareType.Own) {
|
if (isSharingOwnLocation(this.props.shareType)) {
|
||||||
this.geolocate.on('geolocate', this.onGeolocate);
|
this.geolocate.on('geolocate', this.onGeolocate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +188,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
||||||
logger.error("Could not fetch location", e);
|
logger.error("Could not fetch location", e);
|
||||||
// close the dialog and show an error when trying to share own location
|
// close the dialog and show an error when trying to share own location
|
||||||
// pin drop location without permissions is ok
|
// pin drop location without permissions is ok
|
||||||
if (this.props.shareType === LocationShareType.Own) {
|
if (isSharingOwnLocation(this.props.shareType)) {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Could not fetch location',
|
'Could not fetch location',
|
||||||
|
@ -225,6 +222,8 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userColorClass = getUserNameColorClass(this.props.sender.userId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_LocationPicker">
|
<div className="mx_LocationPicker">
|
||||||
<div id="mx_LocationPicker_map" />
|
<div id="mx_LocationPicker_map" />
|
||||||
|
@ -249,9 +248,14 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MLocationBody_marker" id={this.getMarkerId()}>
|
<div className={classNames(
|
||||||
|
"mx_MLocationBody_marker",
|
||||||
|
`mx_MLocationBody_marker-${this.props.shareType}`,
|
||||||
|
userColorClass,
|
||||||
|
)}
|
||||||
|
id={this.getMarkerId()}>
|
||||||
<div className="mx_MLocationBody_markerBorder">
|
<div className="mx_MLocationBody_markerBorder">
|
||||||
{ this.props.shareType === LocationShareType.Own ?
|
{ isSharingOwnLocation(this.props.shareType) ?
|
||||||
<MemberAvatar
|
<MemberAvatar
|
||||||
member={this.props.sender}
|
member={this.props.sender}
|
||||||
width={27}
|
width={27}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import React, { HTMLAttributes, useContext } from 'react';
|
||||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
|
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
|
||||||
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
|
|
||||||
import BaseAvatar from '../avatars/BaseAvatar';
|
import BaseAvatar from '../avatars/BaseAvatar';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import Heading from '../typography/Heading';
|
import Heading from '../typography/Heading';
|
||||||
|
@ -34,9 +33,8 @@ const UserAvatar = () => {
|
||||||
// 40 - 2px border
|
// 40 - 2px border
|
||||||
const avatarSize = 36;
|
const avatarSize = 36;
|
||||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||||
const colorClass = getUserNameColorClass(userId);
|
|
||||||
|
|
||||||
return <div className={`mx_ShareType_option-icon ${LocationShareType.Own} ${colorClass}`}>
|
return <div className={`mx_ShareType_option-icon ${LocationShareType.Own}`}>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
idName={userId}
|
idName={userId}
|
||||||
name={displayName}
|
name={displayName}
|
||||||
|
|
|
@ -205,61 +205,66 @@ describe("LocationPicker", () => {
|
||||||
expect(mockGeolocate.trigger).toHaveBeenCalled();
|
expect(mockGeolocate.trigger).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('for Own location share type', () => {
|
const testUserLocationShareTypes = (shareType: LocationShareType.Own | LocationShareType.Live) => {
|
||||||
it('closes and displays error when geolocation errors', () => {
|
describe(`for ${shareType} location share type`, () => {
|
||||||
// suppress expected error log
|
it('closes and displays error when geolocation errors', () => {
|
||||||
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
// suppress expected error log
|
||||||
const onFinished = jest.fn();
|
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
||||||
getComponent({ onFinished });
|
const onFinished = jest.fn();
|
||||||
|
getComponent({ onFinished, shareType });
|
||||||
|
|
||||||
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
||||||
act(() => {
|
act(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
mockMap.emit('load');
|
mockMap.emit('load');
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
mockGeolocate.emit('error', {});
|
mockGeolocate.emit('error', {});
|
||||||
|
});
|
||||||
|
|
||||||
|
// dialog is closed on error
|
||||||
|
expect(onFinished).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
// dialog is closed on error
|
it('sets position on geolocate event', () => {
|
||||||
expect(onFinished).toHaveBeenCalled();
|
const wrapper = getComponent({ shareType });
|
||||||
|
act(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
||||||
|
wrapper.setProps({});
|
||||||
|
});
|
||||||
|
|
||||||
|
// marker added
|
||||||
|
expect(maplibregl.Marker).toHaveBeenCalled();
|
||||||
|
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
|
||||||
|
12.4, 43.2,
|
||||||
|
));
|
||||||
|
// submit button is enabled when position is truthy
|
||||||
|
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
|
||||||
|
expect(wrapper.find('MemberAvatar').length).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits location', () => {
|
||||||
|
const onChoose = jest.fn();
|
||||||
|
const wrapper = getComponent({ onChoose, shareType });
|
||||||
|
act(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
||||||
|
// make sure button is enabled
|
||||||
|
wrapper.setProps({});
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
// content of this call is tested in LocationShareMenu-test
|
||||||
|
expect(onChoose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
it('sets position on geolocate event', () => {
|
testUserLocationShareTypes(LocationShareType.Own);
|
||||||
const wrapper = getComponent();
|
testUserLocationShareTypes(LocationShareType.Live);
|
||||||
act(() => {
|
|
||||||
// @ts-ignore
|
|
||||||
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
|
||||||
wrapper.setProps({});
|
|
||||||
});
|
|
||||||
|
|
||||||
// marker added
|
|
||||||
expect(maplibregl.Marker).toHaveBeenCalled();
|
|
||||||
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
|
|
||||||
12.4, 43.2,
|
|
||||||
));
|
|
||||||
// submit button is enabled when position is truthy
|
|
||||||
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
|
|
||||||
expect(wrapper.find('MemberAvatar').length).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('submits location', () => {
|
|
||||||
const onChoose = jest.fn();
|
|
||||||
const wrapper = getComponent({ onChoose });
|
|
||||||
act(() => {
|
|
||||||
// @ts-ignore
|
|
||||||
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
|
|
||||||
// make sure button is enabled
|
|
||||||
wrapper.setProps({});
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
|
|
||||||
});
|
|
||||||
|
|
||||||
// content of this call is tested in LocationShareMenu-test
|
|
||||||
expect(onChoose).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for Pin drop location share type', () => {
|
describe('for Pin drop location share type', () => {
|
||||||
const shareType = LocationShareType.Pin;
|
const shareType = LocationShareType.Pin;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue