Live location sharing - basic maximised beacon map (#8310)
* open a dialog with map centered around first beacon Signed-off-by: Kerry Archibald <kerrya@element.io> * add room member markers Signed-off-by: Kerry Archibald <kerrya@element.io> * fix unmount issue in smart marker Signed-off-by: Kerry Archibald <kerrya@element.io> * dont throw on no more live locations Signed-off-by: Kerry Archibald <kerrya@element.io> * cursor on beacon maps Signed-off-by: Kerry Archibald <kerrya@element.io> * fussy import ordering Signed-off-by: Kerry Archibald <kerrya@element.io> * test dialog opening from beacon body Signed-off-by: Kerry Archibald <kerrya@element.io> * test beaconmarker Signed-off-by: Kerry Archibald <kerrya@element.io> * test BeaconViewDialog Signed-off-by: Kerry Archibald <kerrya@element.io> * comment Signed-off-by: Kerry Archibald <kerrya@element.io> * use unstable prefix for wk tile_Server Signed-off-by: Kerry Archibald <kerrya@element.io> * unstable prefix for new m.tile_server use in test Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
1c215e2b71
commit
f95106d2c6
20 changed files with 894 additions and 56 deletions
64
src/components/views/beacon/BeaconMarker.tsx
Normal file
64
src/components/views/beacon/BeaconMarker.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import {
|
||||
Beacon,
|
||||
BeaconEvent,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import SmartMarker from '../location/SmartMarker';
|
||||
|
||||
interface Props {
|
||||
map: maplibregl.Map;
|
||||
beacon: Beacon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a map SmartMarker with latest location from given beacon
|
||||
*/
|
||||
const BeaconMarker: React.FC<Props> = ({ map, beacon }) => {
|
||||
const latestLocationState = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.LocationUpdate,
|
||||
() => beacon.latestLocationState,
|
||||
);
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
const room = matrixClient.getRoom(beacon.roomId);
|
||||
|
||||
if (!latestLocationState || !beacon.isLive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const geoUri = latestLocationState?.uri;
|
||||
|
||||
const markerRoomMember = beacon.beaconInfo.assetType === LocationAssetType.Self ?
|
||||
room.getMember(beacon.beaconInfoOwner) :
|
||||
undefined;
|
||||
|
||||
return <SmartMarker
|
||||
map={map}
|
||||
id={beacon.identifier}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default BeaconMarker;
|
86
src/components/views/beacon/BeaconViewDialog.tsx
Normal file
86
src/components/views/beacon/BeaconViewDialog.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import {
|
||||
Beacon,
|
||||
Room,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
|
||||
import { useLiveBeacons } from '../../../utils/beacon/useLiveBeacons';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||
import Map from '../location/Map';
|
||||
import ZoomButtons from '../location/ZoomButtons';
|
||||
import BeaconMarker from './BeaconMarker';
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
roomId: Room['roomId'];
|
||||
matrixClient: MatrixClient;
|
||||
}
|
||||
|
||||
// TODO actual center is coming soon
|
||||
// for now just center around first beacon in list
|
||||
const getMapCenterUri = (beacons: Beacon[]): string => {
|
||||
const firstBeaconWithLocation = beacons.find(beacon => beacon.latestLocationState);
|
||||
|
||||
return firstBeaconWithLocation?.latestLocationState?.uri;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog to view live beacons maximised
|
||||
*/
|
||||
const BeaconViewDialog: React.FC<IProps> = ({ roomId, matrixClient, onFinished }) => {
|
||||
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
||||
|
||||
const mapCenterUri = getMapCenterUri(liveBeacons);
|
||||
// TODO probably show loader or placeholder when there is no location
|
||||
// to center the map on
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className='mx_BeaconViewDialog'
|
||||
onFinished={onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
<Map
|
||||
id='mx_BeaconViewDialog'
|
||||
centerGeoUri={mapCenterUri}
|
||||
interactive
|
||||
className="mx_BeaconViewDialog_map"
|
||||
>
|
||||
{
|
||||
({ map }: { map: maplibregl.Map}) =>
|
||||
<>
|
||||
{ liveBeacons.map(beacon => <BeaconMarker
|
||||
key={beacon.identifier}
|
||||
map={map}
|
||||
beacon={beacon}
|
||||
/>) }
|
||||
<ZoomButtons map={map} />
|
||||
</>
|
||||
}
|
||||
</Map>
|
||||
</MatrixClientContext.Provider>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeaconViewDialog;
|
|
@ -34,6 +34,9 @@ interface IState {
|
|||
error: Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog to view m.location events maximised
|
||||
*/
|
||||
export default class LocationViewDialog extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -72,12 +72,21 @@ interface SmartMarkerProps {
|
|||
const SmartMarker: React.FC<SmartMarkerProps> = ({ id, map, geoUri, roomMember, useMemberColor }) => {
|
||||
const { onElementRef } = useMapMarker(map, geoUri);
|
||||
|
||||
return <Marker
|
||||
ref={onElementRef}
|
||||
id={id}
|
||||
roomMember={roomMember}
|
||||
useMemberColor={useMemberColor}
|
||||
/>;
|
||||
return (
|
||||
// maplibregl hijacks the Marker dom element
|
||||
// and removes it from the dom when the maplibregl.Marker instance
|
||||
// is removed
|
||||
// wrap in a span so that react doesn't get confused
|
||||
// when trying to unmount this component
|
||||
<span>
|
||||
<Marker
|
||||
ref={onElementRef}
|
||||
id={id}
|
||||
roomMember={roomMember}
|
||||
useMemberColor={useMemberColor}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmartMarker;
|
||||
|
|
|
@ -23,6 +23,7 @@ import { Icon as LocationMarkerIcon } from '../../../../res/img/element-icons/lo
|
|||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import { useBeacon } from '../../../utils/beacon';
|
||||
import { isSelfLocation } from '../../../utils/location';
|
||||
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
|
||||
|
@ -32,6 +33,7 @@ import Map from '../location/Map';
|
|||
import SmartMarker from '../location/SmartMarker';
|
||||
import OwnBeaconStatus from '../beacon/OwnBeaconStatus';
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import BeaconViewDialog from '../beacon/BeaconViewDialog';
|
||||
|
||||
const useBeaconState = (beaconInfoEvent: MatrixEvent): {
|
||||
beacon?: Beacon;
|
||||
|
@ -85,13 +87,31 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
|
|||
latestLocationState,
|
||||
} = useBeaconState(mxEvent);
|
||||
const mapId = useUniqueId(mxEvent.getId());
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
const [error, setError] = useState<Error>();
|
||||
const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error);
|
||||
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
|
||||
|
||||
const isOwnBeacon = beacon?.beaconInfoOwner === matrixClient.getUserId();
|
||||
|
||||
const onClick = () => {
|
||||
if (displayStatus !== BeaconDisplayStatus.Active) {
|
||||
return;
|
||||
}
|
||||
Modal.createTrackedDialog(
|
||||
'Beacon View',
|
||||
'',
|
||||
BeaconViewDialog,
|
||||
{
|
||||
roomId: mxEvent.getRoomId(),
|
||||
matrixClient,
|
||||
},
|
||||
"mx_BeaconViewDialog_wrapper",
|
||||
false, // isPriority
|
||||
true, // isStatic
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='mx_MBeaconBody' ref={ref}>
|
||||
{ displayStatus === BeaconDisplayStatus.Active ?
|
||||
|
@ -99,6 +119,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
|
|||
id={mapId}
|
||||
centerGeoUri={latestLocationState.uri}
|
||||
onError={setError}
|
||||
onClick={onClick}
|
||||
className="mx_MBeaconBody_map"
|
||||
>
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ import { MatrixClientPeg } from '../MatrixClientPeg';
|
|||
const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour";
|
||||
const E2EE_WK_KEY = "io.element.e2ee";
|
||||
const E2EE_WK_KEY_DEPRECATED = "im.vector.riot.e2ee";
|
||||
const TILE_SERVER_WK_KEY = new UnstableValue(
|
||||
export const TILE_SERVER_WK_KEY = new UnstableValue(
|
||||
"m.tile_server", "org.matrix.msc3488.tile_server");
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
|
41
src/utils/beacon/useLiveBeacons.ts
Normal file
41
src/utils/beacon/useLiveBeacons.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Beacon,
|
||||
Room,
|
||||
RoomStateEvent,
|
||||
MatrixClient,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useEventEmitterState } from "../../hooks/useEventEmitter";
|
||||
|
||||
/**
|
||||
* Returns an array of all live beacon ids for a given room
|
||||
*
|
||||
* Beacons are removed from array when they become inactive
|
||||
*/
|
||||
export const useLiveBeacons = (roomId: Room['roomId'], matrixClient: MatrixClient): Beacon[] => {
|
||||
const room = matrixClient.getRoom(roomId);
|
||||
|
||||
const liveBeacons = useEventEmitterState(
|
||||
room.currentState,
|
||||
RoomStateEvent.BeaconLiveness,
|
||||
() => room.currentState?.liveBeaconIds.map(beaconIdentifier => room.currentState.beacons.get(beaconIdentifier)),
|
||||
);
|
||||
|
||||
return liveBeacons;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue