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
parent
b9da2255c4
commit
b98739056e
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
`;
|
Loading…
Reference in New Issue