Live location sharing - extract location markers into generic Marker (#8225)

* extract location markers into generic Marker

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

* comments

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

* remove skinned

Signed-off-by: Kerry Archibald <kerrya@element.io>
pull/21833/head
Kerry 2022-04-06 13:40:39 +02:00 committed by GitHub
parent b9da2255c4
commit b98739056e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 188 additions and 145 deletions

View File

@ -10,6 +10,7 @@
@import "./components/views/location/_LiveDurationDropdown.scss";
@import "./components/views/location/_LocationShareMenu.scss";
@import "./components/views/location/_MapError.scss";
@import "./components/views/location/_Marker.scss";
@import "./components/views/location/_ShareDialogButtons.scss";
@import "./components/views/location/_ShareType.scss";
@import "./components/views/spaces/_QuickThemeSwitcher.scss";

View File

@ -0,0 +1,46 @@
/*
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.
*/
.mx_Marker_defaultColor {
color: $accent;
}
.mx_Marker_border {
width: 42px;
height: 42px;
border-radius: 50%;
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
background-color: currentColor;
display: flex;
justify-content: center;
align-items: center;
// caret down
&::before {
content: '';
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid currentColor;
position: absolute;
bottom: -4px;
}
}
.mx_Marker_icon {
color: white;
height: 20px;
}

View File

@ -55,39 +55,6 @@ limitations under the License.
.maplibregl-user-location-dot {
display: none;
}
.mx_MLocationBody_markerBorder {
width: 31px;
height: 31px;
border-radius: 50%;
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
background-color: currentColor;
display: flex;
align-items: center;
justify-content: center;
}
.mx_MLocationBody_pointer {
position: absolute;
bottom: -3px;
left: 11px;
width: 9px;
height: 5px;
&::before {
mask-image: url('$(res)/img/location/pointer.svg');
mask-position: center;
mask-repeat: no-repeat;
mask-size: 9px;
content: '';
display: inline-block;
width: 9px;
height: 5px;
position: absolute;
background-color: currentColor;
}
}
}
.mx_LocationPicker_footer {
@ -106,11 +73,6 @@ limitations under the License.
}
}
.mx_MLocationBody_markerIcon {
color: white;
height: 20px;
}
.mx_LocationPicker_pinText {
position: absolute;
top: $spacing-16;
@ -135,11 +97,3 @@ limitations under the License.
width: 100%;
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;
}

View File

@ -22,56 +22,6 @@ limitations under the License.
border-radius: $timeline-image-border-radius;
}
.mx_MLocationBody_markerBorder {
width: 31px;
height: 31px;
border-radius: 50%;
filter: drop-shadow(0px 3px 5px rgba(0, 0, 0, 0.2));
background-color: $accent;
// See _LocationPicker.scss
display: flex;
justify-content: center;
align-items: center;
.mx_BaseAvatar {
margin: 0;
line-height: 1;
}
}
.mx_MLocationBody_pointer {
position: absolute;
bottom: -3px;
left: 11px;
width: 9px;
height: 5px;
&::before {
mask-image: url('$(res)/img/location/pointer.svg');
mask-position: center;
mask-repeat: no-repeat;
mask-size: 9px;
content: '';
display: inline-block;
width: 9px;
height: 5px;
position: absolute;
background-color: $accent;
}
}
.mx_MLocationBody_markerContents {
background-color: $location-marker-color;
margin: 0;
width: 31px;
height: 31px;
mask-repeat: no-repeat;
mask-size: 16px;
mask-position: center;
mask-image: url('$(res)/img/element-icons/location.svg');
}
}
/* In the timeline, we fit the width of the container */

View File

@ -19,23 +19,20 @@ import maplibregl, { MapMouseEvent } from 'maplibre-gl';
import { logger } from "matrix-js-sdk/src/logger";
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
import classNames from 'classnames';
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
import { _t } from '../../../languageHandler';
import MatrixClientContext from '../../../contexts/MatrixClientContext';
import Modal from '../../../Modal';
import SdkConfig from '../../../SdkConfig';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
import { GenericPosition, genericPositionFromGeolocation, getGeoUri } from '../../../utils/beacon';
import { LocationShareError, findMapStyleUrl } from '../../../utils/location';
import MemberAvatar from '../avatars/MemberAvatar';
import ErrorDialog from '../dialogs/ErrorDialog';
import AccessibleButton from '../elements/AccessibleButton';
import { MapError } from './MapError';
import LiveDurationDropdown, { DEFAULT_DURATION_MS } from './LiveDurationDropdown';
import { LocationShareType, ShareLocationFn } from './shareLocation';
import Marker from './Marker';
export interface ILocationPickerProps {
sender: RoomMember;
@ -225,8 +222,6 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
</div>;
}
const userColorClass = getUserNameColorClass(this.props.sender.userId);
return (
<div className="mx_LocationPicker">
<div id="mx_LocationPicker_map" />
@ -256,13 +251,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
</AccessibleButton>
</form>
</div>
<div className={classNames(
"mx_MLocationBody_marker",
`mx_MLocationBody_marker-${this.props.shareType}`,
userColorClass,
)}
id={this.getMarkerId()}
>
<div id={this.getMarkerId()}>
{ /*
maplibregl hijacks the div above to style the marker
it must be in the dom when the map is initialised
@ -271,22 +260,11 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
so hide the internal visible elements
*/ }
{ !!this.marker && <>
<div className="mx_MLocationBody_markerBorder">
{ isSharingOwnLocation(this.props.shareType) ?
<MemberAvatar
member={this.props.sender}
width={27}
height={27}
viewUserOnClick={false}
/>
: <LocationIcon className="mx_MLocationBody_markerIcon" />
}
</div>
<div
className="mx_MLocationBody_pointer"
/>
</> }
{ !!this.marker && <Marker
roomMember={isSharingOwnLocation(this.props.shareType) ? this.props.sender : undefined}
useMemberColor={this.props.shareType === LocationShareType.Live}
/>
}
</div>
</div>
);

View File

@ -0,0 +1,58 @@
/*
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 from 'react';
import classNames from 'classnames';
import { RoomMember } from 'matrix-js-sdk/src/matrix';
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
import { getUserNameColorClass } from '../../../utils/FormattingUtils';
import MemberAvatar from '../avatars/MemberAvatar';
interface Props {
id?: string;
// renders MemberAvatar when provided
roomMember?: RoomMember;
// use member text color as background
useMemberColor?: boolean;
}
/**
* Generic location marker
*/
const Marker: React.FC<Props> = ({ id, roomMember, useMemberColor }) => {
const memberColorClass = useMemberColor && roomMember ? getUserNameColorClass(roomMember.userId) : '';
return <div
id={id}
className={classNames("mx_Marker", memberColorClass, {
"mx_Marker_defaultColor": !memberColorClass,
})}
>
<div className="mx_Marker_border">
{ roomMember ?
<MemberAvatar
member={roomMember}
width={36}
height={36}
viewUserOnClick={false}
/>
: <LocationIcon className="mx_Marker_icon" />
}
</div>
</div>;
};
export default Marker;

View File

@ -26,7 +26,6 @@ import { ClientEvent, IClientWellKnown } from 'matrix-js-sdk/src/client';
import { IBodyProps } from "./IBodyProps";
import { _t } from '../../../languageHandler';
import MemberAvatar from '../avatars/MemberAvatar';
import Modal from '../../../Modal';
import {
parseGeoUri,
@ -41,6 +40,7 @@ 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';
interface IState {
error: Error;
@ -175,16 +175,8 @@ export function LocationBodyContent(props: ILocationBodyContentProps):
className="mx_MLocationBody_map"
/>;
const markerContents = (
isSelfLocation(props.mxEvent.getContent())
? <MemberAvatar
member={props.mxEvent.sender}
width={27}
height={27}
viewUserOnClick={false}
/>
: <div className="mx_MLocationBody_markerContents" />
);
// only pass member to marker when should render avatar marker
const markerRoomMember = isSelfLocation(props.mxEvent.getContent()) ? props.mxEvent.sender : undefined;
return <div className="mx_MLocationBody">
{
@ -198,14 +190,7 @@ export function LocationBodyContent(props: ILocationBodyContentProps):
</TooltipTarget>
: mapDiv
}
<div className="mx_MLocationBody_marker" id={props.markerId}>
<div className="mx_MLocationBody_markerBorder">
{ markerContents }
</div>
<div
className="mx_MLocationBody_pointer"
/>
</div>
<Marker id={props.markerId} roomMember={markerRoomMember} />
{
props.zoomButtons
? <ZoomButtons

View File

@ -301,7 +301,7 @@ describe("LocationPicker", () => {
));
// marker is set, icon not avatar
expect(wrapper.find('.mx_MLocationBody_markerIcon').length).toBeTruthy();
expect(wrapper.find('.mx_Marker_icon').length).toBeTruthy();
});
it('submits location', () => {

View File

@ -0,0 +1,51 @@
/*
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 from 'react';
import { mount } from 'enzyme';
import { RoomMember } from 'matrix-js-sdk/src/matrix';
import Marker from '../../../../src/components/views/location/Marker';
describe('<Marker />', () => {
const defaultProps = {
id: 'abc123',
};
const getComponent = (props = {}) =>
mount(<Marker {...defaultProps} {...props} />);
it('renders with location icon when no room member', () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('does not try to use member color without room member', () => {
const component = getComponent({ useMemberColor: true });
expect(component.find('div').at(0).props().className).toEqual('mx_Marker mx_Marker_defaultColor');
});
it('uses member color class', () => {
const member = new RoomMember('!room:server', '@user:server');
const component = getComponent({ useMemberColor: true, roomMember: member });
expect(component.find('div').at(0).props().className).toEqual('mx_Marker mx_Username_color3');
});
it('renders member avatar when roomMember is truthy', () => {
const member = new RoomMember('!room:server', '@user:server');
const component = getComponent({ roomMember: member });
expect(component.find('MemberAvatar').length).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Marker /> renders with location icon when no room member 1`] = `
<Marker
id="abc123"
>
<div
className="mx_Marker mx_Marker_defaultColor"
id="abc123"
>
<div
className="mx_Marker_border"
>
<div
className="mx_Marker_icon"
/>
</div>
</div>
</Marker>
`;