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 4 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
38 changes: 36 additions & 2 deletions 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 @@ -83,6 +84,8 @@ interface IProps extends IPosition {
interface IState {
canRedact: boolean;
canPin: boolean;
canForward: boolean;
canShare: boolean;
}

@replaceableComponent("views.context_menus.MessageContextMenu")
Expand All @@ -93,6 +96,8 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
state = {
canRedact: false,
canPin: false,
canForward: false,
canShare: false,
};

componentDidMount() {
Expand Down Expand Up @@ -122,7 +127,11 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
if (!SettingsStore.getValue("feature_pinning")) canPin = false;

this.setState({ canRedact, canPin });
const isLoc = isLocationEvent(this.props.mxEvent);
const canForward = !isLoc;
const canShare = !isLoc;

this.setState({ canRedact, canPin, canForward, canShare });
};

private isPinned(): boolean {
Expand All @@ -133,9 +142,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 +291,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 +327,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 +492,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();
}