Simple static location sharing (#7135)

Adds maplibre as a dependency, and behind a labs flag, lets users send and receive [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md) style location shares - with backwards compatibility with old school `m.location` `msgtype` location shares too.

For this to work, you have to define a valid maptile server and API in your config.json's `map_style_url`.
This commit is contained in:
Matthew Hodgson 2021-12-06 09:45:12 +00:00 committed by GitHub
parent eb05044bc4
commit 1262021417
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 703 additions and 3 deletions

View file

@ -48,6 +48,7 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
import { Action } from "../../../dispatcher/actions";
import EditorModel from "../../../editor/model";
import EmojiPicker from '../emojipicker/EmojiPicker';
import LocationPicker from '../location/LocationPicker';
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
import Modal from "../../../Modal";
import { RelationType } from 'matrix-js-sdk/src/@types/event';
@ -55,6 +56,9 @@ import RoomContext from '../../../contexts/RoomContext';
import { POLL_START_EVENT_TYPE } from "../../../polls/consts";
import ErrorDialog from "../dialogs/ErrorDialog";
import PollCreateDialog from "../elements/PollCreateDialog";
import { MsgType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import LocationShareType from "../location/LocationShareType";
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
let instanceCount = 0;
@ -77,7 +81,7 @@ function SendButton(props: ISendButtonProps) {
interface IEmojiButtonProps {
addEmoji: (unicode: string) => boolean;
menuPosition: any; // TODO: Types
menuPosition: AboveLeftOf;
narrowMode: boolean;
}
@ -114,6 +118,46 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narr
</React.Fragment>;
};
interface ILocationButtonProps {
room: Room;
shareLocation: (uri: string, ts: number, type: LocationShareType, description: string) => boolean;
menuPosition: AboveLeftOf;
narrowMode: boolean;
}
const LocationButton: React.FC<ILocationButtonProps> = ({ shareLocation, menuPosition, narrowMode }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
let contextMenu;
if (menuDisplayed) {
const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
<LocationPicker onChoose={shareLocation} onFinished={closeMenu} />
</ContextMenu>;
}
const className = classNames(
"mx_MessageComposer_button",
"mx_MessageComposer_location",
{
"mx_MessageComposer_button_highlight": menuDisplayed,
},
);
// TODO: replace ContextMenuTooltipButton with a unified representation of
// the header buttons and the right panel buttons
return <React.Fragment>
<AccessibleTooltipButton
className={className}
onClick={openMenu}
title={!narrowMode && _t('Share location')}
label={narrowMode ? _t('Share location') : null}
/>
{ contextMenu }
</React.Fragment>;
};
interface IUploadButtonProps {
roomId: string;
relation?: IEventRelation | null;
@ -447,6 +491,25 @@ export default class MessageComposer extends React.Component<IProps, IState> {
return true;
};
private shareLocation = (uri: string, ts: number, type: LocationShareType, description: string): boolean => {
if (!uri) return false;
try {
const text = `${description ? description : 'Location'} at ${uri} as of ${new Date(ts).toISOString()}`;
// noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
"body": text,
"msgtype": MsgType.Location,
"geo_uri": uri,
"org.matrix.msc3488.location": { uri, description },
"org.matrix.msc3488.ts": ts,
// TODO: MSC1767 fallbacks for text & thumbnail
});
} catch (e) {
logger.error("Error sending location:", e);
}
return true;
};
private sendMessage = async () => {
if (this.state.haveRecording && this.voiceRecordingButton.current) {
// There shouldn't be any text message to send when a voice recording is active, so
@ -514,6 +577,17 @@ export default class MessageComposer extends React.Component<IProps, IState> {
relation={this.props.relation}
/>,
);
if (SettingsStore.getValue("feature_location_share")) {
buttons.push(
<LocationButton
key="location"
room={this.props.room}
shareLocation={this.shareLocation}
menuPosition={menuPosition}
narrowMode={this.state.narrowMode}
/>,
);
}
buttons.push(
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,
);