From 09850460af13c8a558b34c54c1f9a528963fb8da Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 25 May 2022 16:11:00 +0200 Subject: [PATCH 1/6] share plain lat,lon string from beacon tooltip and list item Signed-off-by: Kerry Archibald --- .../views/beacon/BeaconListItem.tsx | 7 +-- .../views/beacon/BeaconStatusTooltip.tsx | 7 +-- .../views/beacon/ShareLatestLocation.tsx | 49 +++++++++++++++ .../views/beacon/ShareLatestLocation-test.tsx | 59 +++++++++++++++++++ .../ShareLatestLocation-test.tsx.snap | 55 +++++++++++++++++ 5 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 src/components/views/beacon/ShareLatestLocation.tsx create mode 100644 test/components/views/beacon/ShareLatestLocation-test.tsx create mode 100644 test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap diff --git a/src/components/views/beacon/BeaconListItem.tsx b/src/components/views/beacon/BeaconListItem.tsx index eda1580700e..bcfb4971766 100644 --- a/src/components/views/beacon/BeaconListItem.tsx +++ b/src/components/views/beacon/BeaconListItem.tsx @@ -23,10 +23,10 @@ import { useEventEmitterState } from '../../../hooks/useEventEmitter'; import { humanizeTime } from '../../../utils/humanize'; import { _t } from '../../../languageHandler'; import MemberAvatar from '../avatars/MemberAvatar'; -import CopyableText from '../elements/CopyableText'; import BeaconStatus from './BeaconStatus'; import { BeaconDisplayStatus } from './displayStatus'; import StyledLiveBeaconIcon from './StyledLiveBeaconIcon'; +import ShareLatestLocation from './ShareLatestLocation'; interface Props { beacon: Beacon; @@ -69,10 +69,7 @@ const BeaconListItem: React.FC = ({ beacon }) => { label={beaconMember?.name || beacon.beaconInfo.description || beacon.beaconInfoOwner} displayStatus={BeaconDisplayStatus.Active} > - latestLocationState?.uri} - /> + { _t("Updated %(humanizedUpdateTime)s", { humanizedUpdateTime }) } diff --git a/src/components/views/beacon/BeaconStatusTooltip.tsx b/src/components/views/beacon/BeaconStatusTooltip.tsx index bc9f3609395..688abc510aa 100644 --- a/src/components/views/beacon/BeaconStatusTooltip.tsx +++ b/src/components/views/beacon/BeaconStatusTooltip.tsx @@ -19,9 +19,9 @@ import { Beacon } from 'matrix-js-sdk/src/matrix'; import { LocationAssetType } from 'matrix-js-sdk/src/@types/location'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; -import CopyableText from '../elements/CopyableText'; import BeaconStatus from './BeaconStatus'; import { BeaconDisplayStatus } from './displayStatus'; +import ShareLatestLocation from './ShareLatestLocation'; interface Props { beacon: Beacon; @@ -50,10 +50,7 @@ const BeaconStatusTooltip: React.FC = ({ beacon }) => { displayLiveTimeRemaining className='mx_BeaconStatusTooltip_inner' > - beacon.latestLocationState?.uri} - /> + ; }; diff --git a/src/components/views/beacon/ShareLatestLocation.tsx b/src/components/views/beacon/ShareLatestLocation.tsx new file mode 100644 index 00000000000..a287dd879c8 --- /dev/null +++ b/src/components/views/beacon/ShareLatestLocation.tsx @@ -0,0 +1,49 @@ +/* +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, { useEffect, useState } from 'react'; +import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers'; + +import { parseGeoUri } from '../../../utils/location'; +import CopyableText from '../elements/CopyableText'; + +interface Props { + latestLocationState?: BeaconLocationState; +} + +const ShareLatestLocation: React.FC = ({ latestLocationState }) => { + const [latLonString, setLatLonString] = useState(null); + useEffect(() => { + if (!latestLocationState) { + return; + } + const { latitude, longitude } = parseGeoUri(latestLocationState.uri); + setLatLonString(`${latitude},${longitude}`); + }, [latestLocationState]); + + if (!latestLocationState) { + return null; + } + + return <> + latLonString} + /> + ; +}; + +export default ShareLatestLocation; diff --git a/test/components/views/beacon/ShareLatestLocation-test.tsx b/test/components/views/beacon/ShareLatestLocation-test.tsx new file mode 100644 index 00000000000..9edb6754394 --- /dev/null +++ b/test/components/views/beacon/ShareLatestLocation-test.tsx @@ -0,0 +1,59 @@ +/* +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 { act } from 'react-dom/test-utils'; + +import ShareLatestLocation from '../../../../src/components/views/beacon/ShareLatestLocation'; +import { copyPlaintext } from '../../../../src/utils/strings'; +import { flushPromises } from '../../../test-utils'; + +jest.mock('../../../../src/utils/strings', () => ({ + copyPlaintext: jest.fn().mockResolvedValue(undefined), +})); + +describe('', () => { + const defaultProps = { + latestLocationState: { + uri: 'geo:51,42;u=35', + timestamp: 123, + }, + }; + const getComponent = (props = {}) => + mount(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders null when no location', () => { + const component = getComponent({ latestLocationState: undefined }); + expect(component.html()).toBeNull(); + }); + + it('renders copyable text when there is a location', async () => { + const component = getComponent(); + expect(component).toMatchSnapshot(); + + await act(async () => { + component.find('.mx_CopyableText_copyButton').at(0).simulate('click'); + await flushPromises(); + }); + + expect(copyPlaintext).toHaveBeenCalledWith('51,42'); + }); +}); diff --git a/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap b/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap new file mode 100644 index 00000000000..44dd8d24d0a --- /dev/null +++ b/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders copyable text when there is a location 1`] = ` + + +
+ + +
+ + +
+ + +`; From f25d8939af531c265287151722057e4f5a46e4a1 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 25 May 2022 16:18:23 +0200 Subject: [PATCH 2/6] export makeMapSiteLink helper fn Signed-off-by: Kerry Archibald --- .../views/context_menus/MessageContextMenu.tsx | 4 ++-- src/utils/location/map.ts | 8 ++++---- test/utils/location/map-test.ts | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 725065e95dc..8372ca14bfb 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -50,7 +50,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile"; import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload"; import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload"; -import { createMapSiteLink } from '../../../utils/location'; +import { createMapSiteLinkFromEvent } from '../../../utils/location'; interface IProps extends IPosition { chevronFace: ChevronFace; @@ -360,7 +360,7 @@ export default class MessageContextMenu extends React.Component let openInMapSiteButton: JSX.Element; if (this.canOpenInMapSite(mxEvent)) { - const mapSiteLink = createMapSiteLink(mxEvent); + const mapSiteLink = createMapSiteLinkFromEvent(mxEvent); openInMapSiteButton = ( { +export const makeMapSiteLink = (coords: GeolocationCoordinates): string => { return ( "https://www.openstreetmap.org/" + `?mlat=${coords.latitude}` + @@ -74,18 +74,18 @@ const makeLink = (coords: GeolocationCoordinates): string => { ); }; -export const createMapSiteLink = (event: MatrixEvent): string => { +export const createMapSiteLinkFromEvent = (event: MatrixEvent): string => { const content: Object = event.getContent(); const mLocation = content[M_LOCATION.name]; if (mLocation !== undefined) { const uri = mLocation["uri"]; if (uri !== undefined) { - return makeLink(parseGeoUri(uri)); + return makeMapSiteLink(parseGeoUri(uri)); } } else { const geoUri = content["geo_uri"]; if (geoUri) { - return makeLink(parseGeoUri(geoUri)); + return makeMapSiteLink(parseGeoUri(geoUri)); } } return null; diff --git a/test/utils/location/map-test.ts b/test/utils/location/map-test.ts index f389f12cfdb..d090926f07d 100644 --- a/test/utils/location/map-test.ts +++ b/test/utils/location/map-test.ts @@ -14,20 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { createMapSiteLink } from "../../../src/utils/location"; +import { createMapSiteLinkFromEvent } from "../../../src/utils/location"; import { mkMessage } from "../../test-utils"; import { makeLegacyLocationEvent, makeLocationEvent } from "../../test-utils/location"; -describe("createMapSiteLink", () => { +describe("createMapSiteLinkFromEvent", () => { it("returns null if event does not contain geouri", () => { - expect(createMapSiteLink(mkMessage({ + expect(createMapSiteLinkFromEvent(mkMessage({ room: '1', user: '@sender:server', event: true, }))).toBeNull(); }); it("returns OpenStreetMap link if event contains m.location", () => { expect( - createMapSiteLink(makeLocationEvent("geo:51.5076,-0.1276")), + createMapSiteLinkFromEvent(makeLocationEvent("geo:51.5076,-0.1276")), ).toEqual( "https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + @@ -37,7 +37,7 @@ describe("createMapSiteLink", () => { it("returns OpenStreetMap link if event contains geo_uri", () => { expect( - createMapSiteLink(makeLegacyLocationEvent("geo:51.5076,-0.1276")), + createMapSiteLinkFromEvent(makeLegacyLocationEvent("geo:51.5076,-0.1276")), ).toEqual( "https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + From 8fabbdb8dada6d316eecc71d1d4299eb743928e9 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 25 May 2022 16:35:28 +0200 Subject: [PATCH 3/6] use currentColor in external-link.svg Signed-off-by: Kerry Archibald --- res/img/external-link.svg | 2 +- src/components/views/elements/CopyableText.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/img/external-link.svg b/res/img/external-link.svg index 459e790fe31..cae1446a687 100644 --- a/res/img/external-link.svg +++ b/res/img/external-link.svg @@ -1,5 +1,5 @@ - + diff --git a/src/components/views/elements/CopyableText.tsx b/src/components/views/elements/CopyableText.tsx index f95cbcbd168..8486d008c17 100644 --- a/src/components/views/elements/CopyableText.tsx +++ b/src/components/views/elements/CopyableText.tsx @@ -27,9 +27,10 @@ interface IProps { children?: React.ReactNode; getTextToCopy: () => string; border?: boolean; + className?: string; } -const CopyableText: React.FC = ({ children, getTextToCopy, border=true }) => { +const CopyableText: React.FC = ({ children, getTextToCopy, border=true, className }) => { const [tooltip, setTooltip] = useState(undefined); const onCopyClickInternal = async (e: ButtonEvent) => { @@ -44,11 +45,11 @@ const CopyableText: React.FC = ({ children, getTextToCopy, border=true } } }; - const className = classNames("mx_CopyableText", { + const combinedClassName = classNames("mx_CopyableText", className, { mx_CopyableText_border: border, }); - return
+ return
{ children } Date: Wed, 25 May 2022 16:41:29 +0200 Subject: [PATCH 4/6] add open in openstreetmap link Signed-off-by: Kerry Archibald --- res/css/_components.scss | 1 + .../views/beacon/_BeaconStatusTooltip.scss | 5 ---- .../views/beacon/_ShareLatestLocation.scss | 28 +++++++++++++++++++ .../views/beacon/ShareLatestLocation.tsx | 27 ++++++++++++++---- .../views/beacon/ShareLatestLocation-test.tsx | 2 +- .../BeaconListItem-test.tsx.snap | 2 +- .../ShareLatestLocation-test.tsx.snap | 28 +++++++++++++++++-- 7 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 res/css/components/views/beacon/_ShareLatestLocation.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 9b808463ac6..7e69d2b17fd 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -14,6 +14,7 @@ @import "./components/views/beacon/_LiveTimeRemaining.scss"; @import "./components/views/beacon/_OwnBeaconStatus.scss"; @import "./components/views/beacon/_RoomLiveShareWarning.scss"; +@import "./components/views/beacon/_ShareLatestLocation.scss"; @import "./components/views/beacon/_StyledLiveBeaconIcon.scss"; @import "./components/views/location/_EnableLiveShare.scss"; @import "./components/views/location/_LiveDurationDropdown.scss"; diff --git a/res/css/components/views/beacon/_BeaconStatusTooltip.scss b/res/css/components/views/beacon/_BeaconStatusTooltip.scss index 07b3a43cc01..d6ed72e4552 100644 --- a/res/css/components/views/beacon/_BeaconStatusTooltip.scss +++ b/res/css/components/views/beacon/_BeaconStatusTooltip.scss @@ -21,11 +21,6 @@ limitations under the License. height: 38px; box-sizing: content-box; padding-top: $spacing-8; - - // override copyable text style to make compact - .mx_CopyableText_copyButton { - margin-left: 0 !important; - } } .mx_BeaconStatusTooltip_inner { diff --git a/res/css/components/views/beacon/_ShareLatestLocation.scss b/res/css/components/views/beacon/_ShareLatestLocation.scss new file mode 100644 index 00000000000..796bb55d33a --- /dev/null +++ b/res/css/components/views/beacon/_ShareLatestLocation.scss @@ -0,0 +1,28 @@ +/* +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_ShareLatestLocation_icon { + height: 13px; + width: 13px; + color: $text-secondary-color; +} + +.mx_ShareLatestLocation_copy { + // override copyable text style to make compact + .mx_CopyableText_copyButton { + margin-left: $spacing-8 !important; + } +} diff --git a/src/components/views/beacon/ShareLatestLocation.tsx b/src/components/views/beacon/ShareLatestLocation.tsx index a287dd879c8..c1282dedade 100644 --- a/src/components/views/beacon/ShareLatestLocation.tsx +++ b/src/components/views/beacon/ShareLatestLocation.tsx @@ -17,29 +17,46 @@ limitations under the License. import React, { useEffect, useState } from 'react'; import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers'; -import { parseGeoUri } from '../../../utils/location'; +import { makeMapSiteLink, parseGeoUri } from '../../../utils/location'; import CopyableText from '../elements/CopyableText'; +import { Icon as ExternalLinkIcon } from '../../../../res/img/external-link.svg'; +import { _t } from '../../../languageHandler'; +import TooltipTarget from '../elements/TooltipTarget'; interface Props { latestLocationState?: BeaconLocationState; } const ShareLatestLocation: React.FC = ({ latestLocationState }) => { - const [latLonString, setLatLonString] = useState(null); + const [coords, setCoords] = useState(null); useEffect(() => { if (!latestLocationState) { return; } - const { latitude, longitude } = parseGeoUri(latestLocationState.uri); - setLatLonString(`${latitude},${longitude}`); + const coords = parseGeoUri(latestLocationState.uri); + setCoords(coords); }, [latestLocationState]); - if (!latestLocationState) { + if (!latestLocationState || !coords) { return null; } + const latLonString = `${coords.latitude},${coords.longitude}`; + const mapLink = makeMapSiteLink(coords); + return <> + + + + + latLonString} /> diff --git a/test/components/views/beacon/ShareLatestLocation-test.tsx b/test/components/views/beacon/ShareLatestLocation-test.tsx index 9edb6754394..28d36bc9772 100644 --- a/test/components/views/beacon/ShareLatestLocation-test.tsx +++ b/test/components/views/beacon/ShareLatestLocation-test.tsx @@ -45,7 +45,7 @@ describe('', () => { expect(component.html()).toBeNull(); }); - it('renders copyable text when there is a location', async () => { + it('renders share buttons when there is a location', async () => { const component = getComponent(); expect(component).toMatchSnapshot(); diff --git a/test/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap b/test/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap index 1518a60dba9..221d534c029 100644 --- a/test/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/BeaconListItem-test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` when a beacon is live and has locations renders beacon info 1`] = `"
  • Alice's carLive until 16:04
    Updated a few seconds ago
  • "`; +exports[` when a beacon is live and has locations renders beacon info 1`] = `"
  • Alice's carLive until 16:04
    Updated a few seconds ago
  • "`; diff --git a/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap b/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap index 44dd8d24d0a..5f55d3103d0 100644 --- a/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/ShareLatestLocation-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders copyable text when there is a location 1`] = ` +exports[` renders share buttons when there is a location 1`] = ` renders copyable text when there is a location } } > + +
    + +
    + +
    +
    Date: Wed, 25 May 2022 16:43:48 +0200 Subject: [PATCH 5/6] fussy import ordering Signed-off-by: Kerry Archibald --- src/components/views/beacon/ShareLatestLocation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/beacon/ShareLatestLocation.tsx b/src/components/views/beacon/ShareLatestLocation.tsx index c1282dedade..09c179f6d62 100644 --- a/src/components/views/beacon/ShareLatestLocation.tsx +++ b/src/components/views/beacon/ShareLatestLocation.tsx @@ -17,10 +17,10 @@ limitations under the License. import React, { useEffect, useState } from 'react'; import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers'; -import { makeMapSiteLink, parseGeoUri } from '../../../utils/location'; -import CopyableText from '../elements/CopyableText'; import { Icon as ExternalLinkIcon } from '../../../../res/img/external-link.svg'; import { _t } from '../../../languageHandler'; +import { makeMapSiteLink, parseGeoUri } from '../../../utils/location'; +import CopyableText from '../elements/CopyableText'; import TooltipTarget from '../elements/TooltipTarget'; interface Props { From aa5875a172cfb08a3ef6a2573250d18940296d58 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 27 May 2022 10:43:12 +0200 Subject: [PATCH 6/6] fix icon color var Signed-off-by: Kerry Archibald --- res/css/components/views/beacon/_ShareLatestLocation.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/components/views/beacon/_ShareLatestLocation.scss b/res/css/components/views/beacon/_ShareLatestLocation.scss index 796bb55d33a..5d037fdbd55 100644 --- a/res/css/components/views/beacon/_ShareLatestLocation.scss +++ b/res/css/components/views/beacon/_ShareLatestLocation.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_ShareLatestLocation_icon { height: 13px; width: 13px; - color: $text-secondary-color; + color: $secondary-content; } .mx_ShareLatestLocation_copy {