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

Commit 07b3d4c

Browse files
committed
Allow opening a map view in OpenStreetMap
1 parent 5829dbd commit 07b3d4c

File tree

7 files changed

+143
-2
lines changed

7 files changed

+143
-2
lines changed

res/css/views/context_menus/_MessageContextMenu.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ limitations under the License.
5454
mask-image: url('$(res)/img/element-icons/settings/appearance.svg');
5555
}
5656

57+
.mx_MessageContextMenu_iconOpenInMapSite::before {
58+
mask-image: url('$(res)/img/external-link.svg');
59+
}
60+
5761
.mx_MessageContextMenu_iconEndPoll::before {
5862
mask-image: url('$(res)/img/element-icons/check-white.svg');
5963
}

src/components/views/context_menus/MessageContextMenu.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
4646
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
4747
import EndPollDialog from '../dialogs/EndPollDialog';
4848
import { isPollEnded } from '../messages/MPollBody';
49+
import { createMapSiteLink } from "../messages/MLocationBody";
4950

5051
export function canCancel(eventStatus: EventStatus): boolean {
5152
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@@ -141,9 +142,13 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
141142
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
142143
}
143144

145+
private canOpenInMapSite(mxEvent: MatrixEvent): boolean {
146+
return isLocationEvent(mxEvent);
147+
}
148+
144149
private canEndPoll(mxEvent: MatrixEvent): boolean {
145150
return (
146-
mxEvent.getType() === POLL_START_EVENT_TYPE.name &&
151+
POLL_START_EVENT_TYPE.matches(mxEvent.getType()) &&
147152
this.state.canRedact &&
148153
!isPollEnded(mxEvent, MatrixClientPeg.get(), this.props.getRelationsForEvent)
149154
);
@@ -286,6 +291,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
286291
const eventStatus = mxEvent.status;
287292
const unsentReactionsCount = this.getUnsentReactions().length;
288293

294+
let openInMapSiteButton: JSX.Element;
289295
let endPollButton: JSX.Element;
290296
let resendReactionsButton: JSX.Element;
291297
let redactButton: JSX.Element;
@@ -321,6 +327,25 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
321327
);
322328
}
323329

330+
if (this.canOpenInMapSite(mxEvent)) {
331+
const mapSiteLink = createMapSiteLink(mxEvent);
332+
openInMapSiteButton = (
333+
<IconizedContextMenuOption
334+
iconClassName="mx_MessageContextMenu_iconOpenInMapSite"
335+
onClick={null}
336+
label={_t('Open in OpenStreetMap')}
337+
element="a"
338+
{
339+
...{
340+
href: mapSiteLink,
341+
target: "_blank",
342+
rel: "noreferrer noopener",
343+
}
344+
}
345+
/>
346+
);
347+
}
348+
324349
if (isContentActionable(mxEvent)) {
325350
if (canForward(mxEvent)) {
326351
forwardButton = (
@@ -467,6 +492,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
467492
label={_t("View in room")}
468493
onClick={this.viewInRoom}
469494
/> }
495+
{ openInMapSiteButton }
470496
{ endPollButton }
471497
{ quoteButton }
472498
{ forwardButton }

src/components/views/location/LocationButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,4 @@ export function textForLocation(
9090
}
9191
}
9292

93+
export default LocationButton;

src/components/views/messages/MLocationBody.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import React from 'react';
1818
import maplibregl from 'maplibre-gl';
1919
import { logger } from "matrix-js-sdk/src/logger";
2020
import { LOCATION_EVENT_TYPE } from 'matrix-js-sdk/src/@types/location';
21+
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
2122

2223
import SdkConfig from '../../../SdkConfig';
2324
import { replaceableComponent } from "../../../utils/replaceableComponent";
@@ -147,3 +148,29 @@ export function parseGeoUri(uri: string): GeolocationCoordinates {
147148
speed: undefined,
148149
};
149150
}
151+
152+
function makeLink(coords: GeolocationCoordinates): string {
153+
return (
154+
"https://www.openstreetmap.org/" +
155+
`?mlat=${coords.latitude}` +
156+
`&mlon=${coords.longitude}` +
157+
`#map=16/${coords.latitude}/${coords.longitude}`
158+
);
159+
}
160+
161+
export function createMapSiteLink(event: MatrixEvent): string {
162+
const content: Object = event.getContent();
163+
const mLocation = content[LOCATION_EVENT_TYPE.name];
164+
if (mLocation !== undefined) {
165+
const uri = mLocation["uri"];
166+
if (uri !== undefined) {
167+
return makeLink(parseGeoUri(uri));
168+
}
169+
} else {
170+
const geoUri = content["geo_uri"];
171+
if (geoUri) {
172+
return makeLink(parseGeoUri(geoUri));
173+
}
174+
}
175+
return null;
176+
}

src/components/views/rooms/CollapsibleButton.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ export const CollapsibleButton = ({ narrowMode, title, ...props }: ICollapsibleB
3232
label={narrowMode ? title : undefined}
3333
/>;
3434
};
35+
36+
export default CollapsibleButton;

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,7 @@
28322832
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
28332833
"Unable to reject invite": "Unable to reject invite",
28342834
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",
2835+
"Open in OpenStreetMap": "Open in OpenStreetMap",
28352836
"Forward": "Forward",
28362837
"View source": "View source",
28372838
"Show preview": "Show preview",

test/components/views/messages/MLocationBody-test.tsx

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
18+
import { LOCATION_EVENT_TYPE } from "matrix-js-sdk/src/@types/location";
19+
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
20+
1721
import sdk from "../../../skinned-sdk";
18-
import { parseGeoUri } from "../../../../src/components/views/messages/MLocationBody";
22+
import { createMapSiteLink, parseGeoUri } from "../../../../src/components/views/messages/MLocationBody";
1923

2024
sdk.getComponent("views.messages.MLocationBody");
2125

@@ -159,4 +163,80 @@ describe("MLocationBody", () => {
159163
);
160164
});
161165
});
166+
167+
describe("createMapSiteLink", () => {
168+
it("returns null if event does not contain geouri", () => {
169+
expect(createMapSiteLink(nonLocationEvent())).toBeNull();
170+
});
171+
172+
it("returns OpenStreetMap link if event contains m.location", () => {
173+
expect(
174+
createMapSiteLink(modernLocationEvent("geo:51.5076,-0.1276")),
175+
).toEqual(
176+
"https://www.openstreetmap.org/" +
177+
"?mlat=51.5076&mlon=-0.1276" +
178+
"#map=16/51.5076/-0.1276",
179+
);
180+
});
181+
182+
it("returns OpenStreetMap link if event contains geo_uri", () => {
183+
expect(
184+
createMapSiteLink(oldLocationEvent("geo:51.5076,-0.1276")),
185+
).toEqual(
186+
"https://www.openstreetmap.org/" +
187+
"?mlat=51.5076&mlon=-0.1276" +
188+
"#map=16/51.5076/-0.1276",
189+
);
190+
});
191+
});
162192
});
193+
194+
function oldLocationEvent(geoUri: string): MatrixEvent {
195+
return new MatrixEvent(
196+
{
197+
"event_id": nextId(),
198+
"type": LOCATION_EVENT_TYPE.name,
199+
"content": {
200+
"body": "Something about where I am",
201+
"msgtype": "m.location",
202+
"geo_uri": geoUri,
203+
},
204+
},
205+
);
206+
}
207+
208+
function modernLocationEvent(geoUri: string): MatrixEvent {
209+
return new MatrixEvent(
210+
{
211+
"event_id": nextId(),
212+
"type": LOCATION_EVENT_TYPE.name,
213+
"content": makeLocationContent(
214+
`Found at ${geoUri} at 2021-12-21T12:22+0000`,
215+
geoUri,
216+
252523,
217+
"Human-readable label",
218+
),
219+
},
220+
);
221+
}
222+
223+
function nonLocationEvent(): MatrixEvent {
224+
return new MatrixEvent(
225+
{
226+
"event_id": nextId(),
227+
"type": "some.event.type",
228+
"content": {
229+
"m.relates_to": {
230+
"rel_type": "m.reference",
231+
"event_id": "$mypoll",
232+
},
233+
},
234+
},
235+
);
236+
}
237+
238+
let EVENT_ID = 0;
239+
function nextId(): string {
240+
EVENT_ID++;
241+
return EVENT_ID.toString();
242+
}

0 commit comments

Comments
 (0)