Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 661e2c2

Browse files
author
Kerry
authored
Live location sharing - beacon map in timeline (#8286)
* add displaystatus util Signed-off-by: Kerry Archibald <[email protected]> * map fallback svg Signed-off-by: Kerry Archibald <[email protected]> * add Map to mbeaconbody Signed-off-by: Kerry Archibald <[email protected]> * add bubble layout handling Signed-off-by: Kerry Archibald <[email protected]> * test beaconbody Signed-off-by: Kerry Archibald <[email protected]> * typo Signed-off-by: Kerry Archibald <[email protected]> * use randomString from js-sdk Signed-off-by: Kerry Archibald <[email protected]>
1 parent 4b7840b commit 661e2c2

File tree

8 files changed

+200
-98
lines changed

8 files changed

+200
-98
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
.mx_MBeaconBody {
18+
position: relative;
19+
height: 220px;
20+
width: 325px;
21+
22+
border-radius: $timeline-image-border-radius;
23+
overflow: hidden;
24+
}
25+
26+
.mx_MBeaconBody_map {
27+
height: 100%;
28+
width: 100%;
29+
z-index: 0; // keeps the entire map under the message action bar
30+
}
31+
32+
.mx_MBeaconBody_mapFallback {
33+
box-sizing: border-box;
34+
display: flex;
35+
justify-content: center;
36+
align-items: center;
37+
38+
// pushes spinner/icon up
39+
// to appear more centered with the footer
40+
padding-bottom: 50px;
41+
42+
background: url('$(res)/img/location/map.svg');
43+
background-size: cover;
44+
}
45+
46+
.mx_MBeaconBody_mapFallbackIcon {
47+
width: 65px;
48+
color: $quaternary-content;
49+
}
50+
51+
.mx_EventTile[data-layout="bubble"] .mx_EventTile_line .mx_MBeaconBody {
52+
max-width: 100%;
53+
width: 450px;
54+
}

res/css/views/rooms/_EventBubbleTile.scss

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ limitations under the License.
130130
.mx_MImageBody::before,
131131
.mx_MVideoBody .mx_MVideoBody_container,
132132
.mx_MediaBody,
133-
.mx_MLocationBody_map {
133+
.mx_MLocationBody_map,
134+
.mx_MBeaconBody {
134135
border-bottom-right-radius: var(--cornerRadius) !important;
135136
}
136137
}
@@ -155,7 +156,8 @@ limitations under the License.
155156
.mx_MImageBody::before,
156157
.mx_MVideoBody .mx_MVideoBody_container,
157158
.mx_MediaBody,
158-
.mx_MLocationBody_map {
159+
.mx_MLocationBody_map,
160+
.mx_MBeaconBody {
159161
border-bottom-left-radius: var(--cornerRadius) !important;
160162
}
161163
}
@@ -300,7 +302,8 @@ limitations under the License.
300302
.mx_MVideoBody .mx_MVideoBody_container,
301303
.mx_MImageBody::before,
302304
.mx_MediaBody,
303-
.mx_MLocationBody_map {
305+
.mx_MLocationBody_map,
306+
.mx_MBeaconBody {
304307
border-top-left-radius: 0;
305308
}
306309
}
@@ -311,7 +314,8 @@ limitations under the License.
311314
.mx_MVideoBody .mx_MVideoBody_container,
312315
.mx_MImageBody::before,
313316
.mx_MediaBody,
314-
.mx_MLocationBody_map {
317+
.mx_MLocationBody_map,
318+
.mx_MBeaconBody {
315319
border-bottom-left-radius: var(--cornerRadius);
316320
}
317321
}
@@ -323,7 +327,8 @@ limitations under the License.
323327
.mx_MVideoBody .mx_MVideoBody_container,
324328
.mx_MImageBody::before,
325329
.mx_MediaBody,
326-
.mx_MLocationBody_map {
330+
.mx_MLocationBody_map,
331+
.mx_MBeaconBody {
327332
border-top-right-radius: 0;
328333
}
329334
}
@@ -334,7 +339,8 @@ limitations under the License.
334339
.mx_MVideoBody .mx_MVideoBody_container,
335340
.mx_MImageBody::before,
336341
.mx_MediaBody,
337-
.mx_MLocationBody_map {
342+
.mx_MLocationBody_map,
343+
.mx_MBeaconBody {
338344
border-bottom-right-radius: var(--cornerRadius);
339345
}
340346
}

res/img/location/map.svg

Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { BeaconLocationState } from "matrix-js-sdk/src/content-helpers";
18+
19+
export enum BeaconDisplayStatus {
20+
Loading = 'Loading',
21+
Error = 'Error',
22+
Stopped = 'Stopped',
23+
Active = 'Active',
24+
}
25+
export const getBeaconDisplayStatus = (
26+
isLive: boolean,
27+
latestLocationState?: BeaconLocationState,
28+
error?: Error): BeaconDisplayStatus => {
29+
if (error) {
30+
return BeaconDisplayStatus.Error;
31+
}
32+
if (!isLive) {
33+
return BeaconDisplayStatus.Stopped;
34+
}
35+
36+
if (!latestLocationState) {
37+
return BeaconDisplayStatus.Loading;
38+
}
39+
if (latestLocationState) {
40+
return BeaconDisplayStatus.Active;
41+
}
42+
};

src/components/views/messages/MBeaconBody.tsx

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,23 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React from 'react';
18-
import { BeaconEvent, MatrixEvent } from 'matrix-js-sdk/src/matrix';
17+
import React, { useEffect, useState } from 'react';
18+
import { Beacon, BeaconEvent, MatrixEvent } from 'matrix-js-sdk/src/matrix';
1919
import { BeaconLocationState } from 'matrix-js-sdk/src/content-helpers';
20+
import { randomString } from 'matrix-js-sdk/src/randomstring';
2021

21-
import { IBodyProps } from "./IBodyProps";
22+
import { Icon as LocationMarkerIcon } from '../../../../res/img/element-icons/location.svg';
2223
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
2324
import { useBeacon } from '../../../utils/beacon';
25+
import { isSelfLocation } from '../../../utils/location';
26+
import { BeaconDisplayStatus, getBeaconDisplayStatus } from '../beacon/displayStatus';
27+
import Spinner from '../elements/Spinner';
28+
import Map from '../location/Map';
29+
import SmartMarker from '../location/SmartMarker';
30+
import { IBodyProps } from "./IBodyProps";
2431

2532
const useBeaconState = (beaconInfoEvent: MatrixEvent): {
26-
hasBeacon: boolean;
33+
beacon?: Beacon;
2734
description?: string;
2835
latestLocationState?: BeaconLocationState;
2936
isLive?: boolean;
@@ -41,42 +48,71 @@ const useBeaconState = (beaconInfoEvent: MatrixEvent): {
4148
() => beacon?.latestLocationState);
4249

4350
if (!beacon) {
44-
return {
45-
hasBeacon: false,
46-
};
51+
return {};
4752
}
4853

4954
const { description } = beacon.beaconInfo;
5055

5156
return {
52-
hasBeacon: true,
57+
beacon,
5358
description,
5459
isLive,
5560
latestLocationState,
5661
};
5762
};
5863

59-
const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, ...rest }, ref) => {
64+
// multiple instances of same map might be in document
65+
// eg thread and main timeline, reply
66+
// maplibregl needs a unique id to attach the map instance to
67+
const useUniqueId = (eventId: string): string => {
68+
const [id, setId] = useState(`${eventId}_${randomString(8)}`);
69+
70+
useEffect(() => {
71+
setId(`${eventId}_${randomString(8)}`);
72+
}, [eventId]);
73+
74+
return id;
75+
};
76+
77+
const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) => {
6078
const {
61-
hasBeacon,
6279
isLive,
63-
description,
6480
latestLocationState,
6581
} = useBeaconState(mxEvent);
82+
const mapId = useUniqueId(mxEvent.getId());
6683

67-
if (!hasBeacon || !isLive) {
68-
// TODO stopped, error states
69-
return <span ref={ref}>Beacon stopped or replaced</span>;
70-
}
84+
const [error, setError] = useState<Error>();
85+
86+
const displayStatus = getBeaconDisplayStatus(isLive, latestLocationState, error);
87+
88+
const markerRoomMember = isSelfLocation(mxEvent.getContent()) ? mxEvent.sender : undefined;
7189

7290
return (
73-
// TODO nice map
7491
<div className='mx_MBeaconBody' ref={ref}>
75-
<code>{ mxEvent.getId() }</code>&nbsp;
76-
<span>Beacon "{ description }" </span>
77-
{ latestLocationState ?
78-
<span>{ `${latestLocationState.uri} at ${latestLocationState.timestamp}` }</span> :
79-
<span data-test-id='beacon-waiting-for-location'>Waiting for location</span> }
92+
{ displayStatus === BeaconDisplayStatus.Active ?
93+
<Map
94+
id={mapId}
95+
centerGeoUri={latestLocationState.uri}
96+
onError={setError}
97+
className="mx_MBeaconBody_map"
98+
>
99+
{
100+
({ map }) =>
101+
<SmartMarker
102+
map={map}
103+
id={`${mapId}-marker`}
104+
geoUri={latestLocationState.uri}
105+
roomMember={markerRoomMember}
106+
/>
107+
}
108+
</Map>
109+
: <div className='mx_MBeaconBody_map mx_MBeaconBody_mapFallback'>
110+
{ displayStatus === BeaconDisplayStatus.Loading ?
111+
<Spinner h={32} w={32} /> :
112+
<LocationMarkerIcon className='mx_MBeaconBody_mapFallbackIcon' />
113+
}
114+
</div>
115+
}
80116
</div>
81117
);
82118
});

0 commit comments

Comments
 (0)