From b98739056e40ffd7e7832e8091a3ada524806c11 Mon Sep 17 00:00:00 2001 From: Kerry Date: Wed, 6 Apr 2022 13:40:39 +0200 Subject: [PATCH] Live location sharing - extract location markers into generic Marker (#8225) * extract location markers into generic Marker Signed-off-by: Kerry Archibald * comments Signed-off-by: Kerry Archibald * remove skinned Signed-off-by: Kerry Archibald --- res/css/_components.scss | 1 + .../components/views/location/_Marker.scss | 46 +++++++++++++++ res/css/views/location/_LocationPicker.scss | 46 --------------- res/css/views/messages/_MLocationBody.scss | 50 ---------------- .../views/location/LocationPicker.tsx | 36 +++--------- src/components/views/location/Marker.tsx | 58 +++++++++++++++++++ .../views/messages/MLocationBody.tsx | 23 ++------ .../views/location/LocationPicker-test.tsx | 2 +- .../components/views/location/Marker-test.tsx | 51 ++++++++++++++++ .../__snapshots__/Marker-test.tsx.snap | 20 +++++++ 10 files changed, 188 insertions(+), 145 deletions(-) create mode 100644 res/css/components/views/location/_Marker.scss create mode 100644 src/components/views/location/Marker.tsx create mode 100644 test/components/views/location/Marker-test.tsx create mode 100644 test/components/views/location/__snapshots__/Marker-test.tsx.snap diff --git a/res/css/_components.scss b/res/css/_components.scss index 3f3038ccfdf..09a5fd6e148 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -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"; diff --git a/res/css/components/views/location/_Marker.scss b/res/css/components/views/location/_Marker.scss new file mode 100644 index 00000000000..7a1baccf9f1 --- /dev/null +++ b/res/css/components/views/location/_Marker.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; +} diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss index 91c3e02bf83..9b513a1fb59 100644 --- a/res/css/views/location/_LocationPicker.scss +++ b/res/css/views/location/_LocationPicker.scss @@ -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; -} diff --git a/res/css/views/messages/_MLocationBody.scss b/res/css/views/messages/_MLocationBody.scss index 0b6bd1b44d0..9c7e2767d74 100644 --- a/res/css/views/messages/_MLocationBody.scss +++ b/res/css/views/messages/_MLocationBody.scss @@ -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 */ diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index c7e72c1949d..ffeb40774bd 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -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 { ; } - const userColorClass = getUserNameColorClass(this.props.sender.userId); - return (
@@ -256,13 +251,7 @@ class LocationPicker extends React.Component {
-
+
{ /* 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 { so hide the internal visible elements */ } - { !!this.marker && <> -
- { isSharingOwnLocation(this.props.shareType) ? - - : - } -
-
- } + { !!this.marker && + }
); diff --git a/src/components/views/location/Marker.tsx b/src/components/views/location/Marker.tsx new file mode 100644 index 00000000000..7978e0d5330 --- /dev/null +++ b/src/components/views/location/Marker.tsx @@ -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 = ({ id, roomMember, useMemberColor }) => { + const memberColorClass = useMemberColor && roomMember ? getUserNameColorClass(roomMember.userId) : ''; + return
+
+ { roomMember ? + + : + } +
+
; +}; + +export default Marker; diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index f56b1ae906f..3a447c7944b 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -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()) - ? - :
- ); + // only pass member to marker when should render avatar marker + const markerRoomMember = isSelfLocation(props.mxEvent.getContent()) ? props.mxEvent.sender : undefined; return
{ @@ -198,14 +190,7 @@ export function LocationBodyContent(props: ILocationBodyContentProps): : mapDiv } -
-
- { markerContents } -
-
-
+ { props.zoomButtons ? { )); // 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', () => { diff --git a/test/components/views/location/Marker-test.tsx b/test/components/views/location/Marker-test.tsx new file mode 100644 index 00000000000..da9ccaf0d66 --- /dev/null +++ b/test/components/views/location/Marker-test.tsx @@ -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('', () => { + const defaultProps = { + id: 'abc123', + }; + const getComponent = (props = {}) => + mount(); + + 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(); + }); +}); diff --git a/test/components/views/location/__snapshots__/Marker-test.tsx.snap b/test/components/views/location/__snapshots__/Marker-test.tsx.snap new file mode 100644 index 00000000000..8030f6448e1 --- /dev/null +++ b/test/components/views/location/__snapshots__/Marker-test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders with location icon when no room member 1`] = ` + +
+
+
+
+
+ +`;