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

Allow opening a map view in OpenStreetMap #7428

Merged
merged 5 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions res/css/views/context_menus/_MessageContextMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/settings/appearance.svg');
}

.mx_MessageContextMenu_iconOpenInMapSite::before {
mask-image: url('$(res)/img/external-link.svg');
}

.mx_MessageContextMenu_iconEndPoll::before {
mask-image: url('$(res)/img/element-icons/check-white.svg');
}
Expand Down
28 changes: 27 additions & 1 deletion src/components/views/context_menus/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import EndPollDialog from '../dialogs/EndPollDialog';
import { isPollEnded } from '../messages/MPollBody';
import { createMapSiteLink } from "../messages/MLocationBody";

export function canCancel(eventStatus: EventStatus): boolean {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
Expand Down Expand Up @@ -133,9 +134,13 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
}

private canOpenInMapSite(mxEvent: MatrixEvent): boolean {
return isLocationEvent(mxEvent);
}

private canEndPoll(mxEvent: MatrixEvent): boolean {
return (
mxEvent.getType() === POLL_START_EVENT_TYPE.name &&
POLL_START_EVENT_TYPE.matches(mxEvent.getType()) &&
andybalaam marked this conversation as resolved.
Show resolved Hide resolved
this.state.canRedact &&
!isPollEnded(mxEvent, MatrixClientPeg.get(), this.props.getRelationsForEvent)
);
Expand Down Expand Up @@ -278,6 +283,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
const eventStatus = mxEvent.status;
const unsentReactionsCount = this.getUnsentReactions().length;

let openInMapSiteButton: JSX.Element;
let endPollButton: JSX.Element;
let resendReactionsButton: JSX.Element;
let redactButton: JSX.Element;
Expand Down Expand Up @@ -313,6 +319,25 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
);
}

if (this.canOpenInMapSite(mxEvent)) {
const mapSiteLink = createMapSiteLink(mxEvent);
openInMapSiteButton = (
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconOpenInMapSite"
onClick={null}
label={_t('Open in OpenStreetMap')}
element="a"
{
...{
href: mapSiteLink,
target: "_blank",
rel: "noreferrer noopener",
}
}
/>
);
}

if (isContentActionable(mxEvent)) {
if (canForward(mxEvent)) {
forwardButton = (
Expand Down Expand Up @@ -459,6 +484,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
label={_t("View in room")}
onClick={this.viewInRoom}
/> }
{ openInMapSiteButton }
{ endPollButton }
{ quoteButton }
{ forwardButton }
Expand Down
1 change: 1 addition & 0 deletions src/components/views/location/LocationButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ export function textForLocation(
}
}

export default LocationButton;
27 changes: 27 additions & 0 deletions src/components/views/messages/MLocationBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import React from 'react';
import maplibregl from 'maplibre-gl';
import { logger } from "matrix-js-sdk/src/logger";
import { LOCATION_EVENT_TYPE } from 'matrix-js-sdk/src/@types/location';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';

import SdkConfig from '../../../SdkConfig';
import { replaceableComponent } from "../../../utils/replaceableComponent";
Expand Down Expand Up @@ -147,3 +148,29 @@ export function parseGeoUri(uri: string): GeolocationCoordinates {
speed: undefined,
};
}

function makeLink(coords: GeolocationCoordinates): string {
return (
"https://www.openstreetmap.org/" +
andybalaam marked this conversation as resolved.
Show resolved Hide resolved
`?mlat=${coords.latitude}` +
`&mlon=${coords.longitude}` +
`#map=16/${coords.latitude}/${coords.longitude}`
);
}

export function createMapSiteLink(event: MatrixEvent): string {
const content: Object = event.getContent();
const mLocation = content[LOCATION_EVENT_TYPE.name];
if (mLocation !== undefined) {
const uri = mLocation["uri"];
if (uri !== undefined) {
return makeLink(parseGeoUri(uri));
}
} else {
const geoUri = content["geo_uri"];
if (geoUri) {
return makeLink(parseGeoUri(geoUri));
}
}
return null;
}
2 changes: 2 additions & 0 deletions src/components/views/rooms/CollapsibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export const CollapsibleButton = ({ narrowMode, title, ...props }: ICollapsibleB
label={narrowMode ? title : undefined}
/>;
};

export default CollapsibleButton;
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2832,6 +2832,7 @@
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Unable to reject invite": "Unable to reject invite",
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",
"Open in OpenStreetMap": "Open in OpenStreetMap",
"Forward": "Forward",
"View source": "View source",
"Show preview": "Show preview",
Expand Down
82 changes: 81 additions & 1 deletion test/components/views/messages/MLocationBody-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
import { LOCATION_EVENT_TYPE } from "matrix-js-sdk/src/@types/location";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";

import sdk from "../../../skinned-sdk";
import { parseGeoUri } from "../../../../src/components/views/messages/MLocationBody";
import { createMapSiteLink, parseGeoUri } from "../../../../src/components/views/messages/MLocationBody";

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

Expand Down Expand Up @@ -159,4 +163,80 @@ describe("MLocationBody", () => {
);
});
});

describe("createMapSiteLink", () => {
it("returns null if event does not contain geouri", () => {
expect(createMapSiteLink(nonLocationEvent())).toBeNull();
});

it("returns OpenStreetMap link if event contains m.location", () => {
expect(
createMapSiteLink(modernLocationEvent("geo:51.5076,-0.1276")),
).toEqual(
"https://www.openstreetmap.org/" +
"?mlat=51.5076&mlon=-0.1276" +
"#map=16/51.5076/-0.1276",
);
});

it("returns OpenStreetMap link if event contains geo_uri", () => {
expect(
createMapSiteLink(oldLocationEvent("geo:51.5076,-0.1276")),
).toEqual(
"https://www.openstreetmap.org/" +
"?mlat=51.5076&mlon=-0.1276" +
"#map=16/51.5076/-0.1276",
);
});
});
});

function oldLocationEvent(geoUri: string): MatrixEvent {
return new MatrixEvent(
{
"event_id": nextId(),
"type": LOCATION_EVENT_TYPE.name,
"content": {
"body": "Something about where I am",
"msgtype": "m.location",
"geo_uri": geoUri,
},
},
);
}

function modernLocationEvent(geoUri: string): MatrixEvent {
return new MatrixEvent(
{
"event_id": nextId(),
"type": LOCATION_EVENT_TYPE.name,
"content": makeLocationContent(
`Found at ${geoUri} at 2021-12-21T12:22+0000`,
geoUri,
252523,
"Human-readable label",
),
},
);
}

function nonLocationEvent(): MatrixEvent {
return new MatrixEvent(
{
"event_id": nextId(),
"type": "some.event.type",
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$mypoll",
},
},
},
);
}

let EVENT_ID = 0;
function nextId(): string {
EVENT_ID++;
return EVENT_ID.toString();
}