Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit ed92071

Browse files
author
Kerry
authored
Live location share - open latest location in map site (#8981)
* move getForwardableBeacon to beacon utils * move event transform type up * add helper to get shareable-as-locaion events * use getShareableLocationEvent in MessageContextMenu * test opening in maplink * fix bad copy pasted tests
1 parent 0026e04 commit ed92071

File tree

10 files changed

+310
-17
lines changed

10 files changed

+310
-17
lines changed

src/components/views/context_menus/MessageContextMenu.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import {
3535
canPinEvent,
3636
editEvent,
3737
isContentActionable,
38-
isLocationEvent,
3938
} from '../../../utils/EventUtils';
4039
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu';
4140
import { ReadPinsEventId } from "../right_panel/types";
@@ -58,6 +57,7 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa
5857
import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
5958
import { createMapSiteLinkFromEvent } from '../../../utils/location';
6059
import { getForwardableEvent } from '../../../events/forward/getForwardableEvent';
60+
import { getShareableLocationEvent } from '../../../events/location/getShareableLocationEvent';
6161

6262
interface IProps extends IPosition {
6363
chevronFace: ChevronFace;
@@ -145,10 +145,6 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
145145
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
146146
}
147147

148-
private canOpenInMapSite(mxEvent: MatrixEvent): boolean {
149-
return isLocationEvent(mxEvent);
150-
}
151-
152148
private canEndPoll(mxEvent: MatrixEvent): boolean {
153149
return (
154150
M_POLL_START.matches(mxEvent.getType()) &&
@@ -369,8 +365,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
369365
}
370366

371367
let openInMapSiteButton: JSX.Element;
372-
if (this.canOpenInMapSite(mxEvent)) {
373-
const mapSiteLink = createMapSiteLinkFromEvent(mxEvent);
368+
const shareableLocationEvent = getShareableLocationEvent(mxEvent, cli);
369+
if (shareableLocationEvent) {
370+
const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent);
374371
openInMapSiteButton = (
375372
<IconizedContextMenuOption
376373
iconClassName="mx_MessageContextMenu_iconOpenInMapSite"

src/events/forward/getForwardableEvent.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { M_POLL_START } from "matrix-events-sdk";
1818
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
1919
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
2020

21-
import { getForwardableBeaconEvent } from "./getForwardableBeacon";
21+
import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation";
2222

2323
/**
2424
* Get forwardable event for a given event
@@ -28,8 +28,11 @@ export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): Matr
2828
if (M_POLL_START.matches(event.getType())) {
2929
return null;
3030
}
31+
32+
// Live location beacons should forward their latest location as a static pin location
33+
// If the beacon is not live, or doesn't have a location forwarding is not allowed
3134
if (M_BEACON_INFO.matches(event.getType())) {
32-
return getForwardableBeaconEvent(event, cli);
35+
return getShareableLocationEventForBeacon(event, cli);
3336
}
3437
return event;
3538
};

src/events/forward/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ limitations under the License.
1616

1717
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
1818

19-
export type ForwardableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null;
19+
export type ActionableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null;

src/events/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
export { getForwardableEvent } from './forward/getForwardableEvent';
18+
export { getShareableLocationEvent } from './location/getShareableLocationEvent';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
18+
import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
19+
20+
import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation";
21+
import { isLocationEvent } from "../../utils/EventUtils";
22+
23+
/**
24+
* Get event that is shareable as a location
25+
* If an event does not have a shareable location, return null
26+
*/
27+
export const getShareableLocationEvent = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => {
28+
if (isLocationEvent(event)) {
29+
return event;
30+
}
31+
32+
if (M_BEACON_INFO.matches(event.getType())) {
33+
return getShareableLocationEventForBeacon(event, cli);
34+
}
35+
return null;
36+
};

src/events/types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
18+
19+
export type ActionableEventTransformFunction = (event: MatrixEvent, cli: MatrixClient) => MatrixEvent | null;

src/events/forward/getForwardableBeacon.ts renamed to src/utils/beacon/getShareableLocation.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
18-
19-
import { ForwardableEventTransformFunction } from "./types";
17+
import {
18+
MatrixClient,
19+
MatrixEvent,
20+
getBeaconInfoIdentifier,
21+
} from "matrix-js-sdk/src/matrix";
2022

2123
/**
22-
* Live location beacons should forward their latest location as a static pin location
23-
* If the beacon is not live, or doesn't have a location forwarding is not allowed
24+
* Beacons should only have shareable locations (open in external mapping tool, forward)
25+
* when they are live and have a location
26+
* If not live, returns null
2427
*/
25-
export const getForwardableBeaconEvent: ForwardableEventTransformFunction = (event, cli) => {
28+
export const getShareableLocationEventForBeacon = (event: MatrixEvent, cli: MatrixClient): MatrixEvent | null => {
2629
const room = cli.getRoom(event.getRoomId());
2730
const beacon = room.currentState.beacons?.get(getBeaconInfoIdentifier(event));
2831
const latestLocationEvent = beacon?.latestLocationEvent;

test/components/views/context_menus/MessageContextMenu-test.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { IRoomState } from "../../../../src/components/structures/RoomView";
3636
import { canEditContent } from "../../../../src/utils/EventUtils";
3737
import { copyPlaintext, getSelectedText } from "../../../../src/utils/strings";
3838
import MessageContextMenu from "../../../../src/components/views/context_menus/MessageContextMenu";
39-
import { makeBeaconEvent, makeBeaconInfoEvent, stubClient } from '../../../test-utils';
39+
import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } from '../../../test-utils';
4040
import dispatcher from '../../../../src/dispatcher/dispatcher';
4141
import SettingsStore from '../../../../src/settings/SettingsStore';
4242
import { ReadPinsEventId } from '../../../../src/components/views/right_panel/types';
@@ -308,6 +308,49 @@ describe('MessageContextMenu', () => {
308308
});
309309
});
310310

311+
describe('open as map link', () => {
312+
it('does not allow opening a plain message in open street maps', () => {
313+
const eventContent = MessageEvent.from("hello");
314+
const menu = createMenuWithContent(eventContent);
315+
expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0);
316+
});
317+
318+
it('does not allow opening a beacon that does not have a shareable location event', () => {
319+
const deadBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: false });
320+
const beacon = new Beacon(deadBeaconEvent);
321+
const beacons = new Map<BeaconIdentifier, Beacon>();
322+
beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon);
323+
const menu = createMenu(deadBeaconEvent, {}, {}, beacons);
324+
expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0);
325+
});
326+
327+
it('allows opening a location event in open street map', () => {
328+
const locationEvent = makeLocationEvent('geo:50,50');
329+
const menu = createMenu(locationEvent);
330+
// exists with a href with the lat/lon from the location event
331+
expect(
332+
menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href,
333+
).toEqual('https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50');
334+
});
335+
336+
it('allows opening a beacon that has a shareable location event', () => {
337+
const liveBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: true });
338+
const beaconLocation = makeBeaconEvent(
339+
'@alice', { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
340+
);
341+
const beacon = new Beacon(liveBeaconEvent);
342+
// @ts-ignore illegally set private prop
343+
beacon._latestLocationEvent = beaconLocation;
344+
const beacons = new Map<BeaconIdentifier, Beacon>();
345+
beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon);
346+
const menu = createMenu(liveBeaconEvent, {}, {}, beacons);
347+
// exists with a href with the lat/lon from the location event
348+
expect(
349+
menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href,
350+
).toEqual('https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41');
351+
});
352+
});
353+
311354
describe("right click", () => {
312355
it('copy button does work as expected', () => {
313356
const text = "hello";
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import {
18+
EventType,
19+
MatrixEvent,
20+
MsgType,
21+
} from "matrix-js-sdk/src/matrix";
22+
23+
import { getForwardableEvent } from "../../../src/events";
24+
import {
25+
getMockClientWithEventEmitter,
26+
makeBeaconEvent,
27+
makeBeaconInfoEvent,
28+
makePollStartEvent,
29+
makeRoomWithBeacons,
30+
} from "../../test-utils";
31+
32+
describe('getForwardableEvent()', () => {
33+
const userId = '@alice:server.org';
34+
const roomId = '!room:server.org';
35+
const client = getMockClientWithEventEmitter({
36+
getRoom: jest.fn(),
37+
});
38+
39+
it('returns the event for a room message', () => {
40+
const alicesMessageEvent = new MatrixEvent({
41+
type: EventType.RoomMessage,
42+
sender: userId,
43+
room_id: roomId,
44+
content: {
45+
msgtype: MsgType.Text,
46+
body: 'Hello',
47+
},
48+
});
49+
50+
expect(getForwardableEvent(alicesMessageEvent, client)).toBe(alicesMessageEvent);
51+
});
52+
53+
it('returns null for a poll start event', () => {
54+
const pollStartEvent = makePollStartEvent('test?', userId);
55+
56+
expect(getForwardableEvent(pollStartEvent, client)).toBe(null);
57+
});
58+
59+
describe('beacons', () => {
60+
it('returns null for a beacon that is not live', () => {
61+
const notLiveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: false });
62+
makeRoomWithBeacons(roomId, client, [notLiveBeacon]);
63+
64+
expect(getForwardableEvent(notLiveBeacon, client)).toBe(null);
65+
});
66+
67+
it('returns null for a live beacon that does not have a location', () => {
68+
const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true });
69+
makeRoomWithBeacons(roomId, client, [liveBeacon]);
70+
71+
expect(getForwardableEvent(liveBeacon, client)).toBe(null);
72+
});
73+
74+
it('returns the latest location event for a live beacon with location', () => {
75+
const liveBeacon = makeBeaconInfoEvent(userId, roomId, { isLive: true }, 'id');
76+
const locationEvent = makeBeaconEvent(userId, {
77+
beaconInfoId: liveBeacon.getId(),
78+
geoUri: 'geo:52,42',
79+
// make sure its in live period
80+
timestamp: Date.now() + 1,
81+
});
82+
makeRoomWithBeacons(roomId, client, [liveBeacon], [locationEvent]);
83+
84+
expect(getForwardableEvent(liveBeacon, client)).toBe(locationEvent);
85+
});
86+
});
87+
});

0 commit comments

Comments
 (0)