Allow opening a map view in OpenStreetMap (#7428)
This commit is contained in:
parent
38634f86d1
commit
a239c456e3
7 changed files with 143 additions and 2 deletions
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -90,3 +90,4 @@ export function textForLocation(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default LocationButton;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -32,3 +32,5 @@ export const CollapsibleButton = ({ narrowMode, title, ...props }: ICollapsibleB
|
||||||
label={narrowMode ? title : undefined}
|
label={narrowMode ? title : undefined}
|
||||||
/>;
|
/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default CollapsibleButton;
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue