Allow opening a map view in OpenStreetMap (#7428)

This commit is contained in:
Andy Balaam 2021-12-21 15:48:20 +00:00 committed by GitHub
parent 38634f86d1
commit a239c456e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 2 deletions

View file

@ -54,6 +54,10 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/settings/appearance.svg'); 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 { .mx_MessageContextMenu_iconEndPoll::before {
mask-image: url('$(res)/img/element-icons/check-white.svg'); mask-image: url('$(res)/img/element-icons/check-white.svg');
} }

View file

@ -46,6 +46,7 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore'; import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
import EndPollDialog from '../dialogs/EndPollDialog'; import EndPollDialog from '../dialogs/EndPollDialog';
import { isPollEnded } from '../messages/MPollBody'; import { isPollEnded } from '../messages/MPollBody';
import { createMapSiteLink } from "../messages/MLocationBody";
export function canCancel(eventStatus: EventStatus): boolean { export function canCancel(eventStatus: EventStatus): boolean {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@ -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()); 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 { private canEndPoll(mxEvent: MatrixEvent): boolean {
return ( return (
mxEvent.getType() === POLL_START_EVENT_TYPE.name && POLL_START_EVENT_TYPE.matches(mxEvent.getType()) &&
this.state.canRedact && this.state.canRedact &&
!isPollEnded(mxEvent, MatrixClientPeg.get(), this.props.getRelationsForEvent) !isPollEnded(mxEvent, MatrixClientPeg.get(), this.props.getRelationsForEvent)
); );
@ -278,6 +283,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
const eventStatus = mxEvent.status; const eventStatus = mxEvent.status;
const unsentReactionsCount = this.getUnsentReactions().length; const unsentReactionsCount = this.getUnsentReactions().length;
let openInMapSiteButton: JSX.Element;
let endPollButton: JSX.Element; let endPollButton: JSX.Element;
let resendReactionsButton: JSX.Element; let resendReactionsButton: JSX.Element;
let redactButton: JSX.Element; let redactButton: JSX.Element;
@ -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 (isContentActionable(mxEvent)) {
if (canForward(mxEvent)) { if (canForward(mxEvent)) {
forwardButton = ( forwardButton = (
@ -459,6 +484,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
label={_t("View in room")} label={_t("View in room")}
onClick={this.viewInRoom} onClick={this.viewInRoom}
/> } /> }
{ openInMapSiteButton }
{ endPollButton } { endPollButton }
{ quoteButton } { quoteButton }
{ forwardButton } { forwardButton }

View file

@ -90,3 +90,4 @@ export function textForLocation(
} }
} }
export default LocationButton;

View file

@ -18,6 +18,7 @@ import React from 'react';
import maplibregl from 'maplibre-gl'; import maplibregl from 'maplibre-gl';
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { LOCATION_EVENT_TYPE } from 'matrix-js-sdk/src/@types/location'; 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 SdkConfig from '../../../SdkConfig';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -147,3 +148,29 @@ export function parseGeoUri(uri: string): GeolocationCoordinates {
speed: undefined, speed: undefined,
}; };
} }
function makeLink(coords: GeolocationCoordinates): string {
return (
"https://www.openstreetmap.org/" +
`?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;
}

View file

@ -32,3 +32,5 @@ export const CollapsibleButton = ({ narrowMode, title, ...props }: ICollapsibleB
label={narrowMode ? title : undefined} label={narrowMode ? title : undefined}
/>; />;
}; };
export default CollapsibleButton;

View file

@ -2832,6 +2832,7 @@
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "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", "Unable to reject invite": "Unable to reject invite",
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",
"Open in OpenStreetMap": "Open in OpenStreetMap",
"Forward": "Forward", "Forward": "Forward",
"View source": "View source", "View source": "View source",
"Show preview": "Show preview", "Show preview": "Show preview",

View file

@ -14,8 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. 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 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"); sdk.getComponent("views.messages.MLocationBody");
@ -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();
}