From 4bd82b8201b5522da87540c5e4710a2d7c864c8a Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 20 Apr 2022 14:33:53 +0200 Subject: [PATCH 1/5] add floating own live sharing eacon status to maximised view Signed-off-by: Kerry Archibald --- res/css/_components.scss | 1 + .../views/beacon/_DialogOwnBeaconStatus.scss | 55 +++++++++++++ .../views/beacon/BeaconViewDialog.tsx | 2 + .../views/beacon/DialogOwnBeaconStatus.tsx | 80 +++++++++++++++++++ .../views/beacon/OwnBeaconStatus.tsx | 6 +- 5 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 res/css/components/views/beacon/_DialogOwnBeaconStatus.scss create mode 100644 src/components/views/beacon/DialogOwnBeaconStatus.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 7032c35f39c..5a00bbf69ae 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -7,6 +7,7 @@ @import "./components/views/beacon/_BeaconListItem.scss"; @import "./components/views/beacon/_BeaconStatus.scss"; @import "./components/views/beacon/_BeaconViewDialog.scss"; +@import "./components/views/beacon/_DialogOwnBeaconStatus.scss"; @import "./components/views/beacon/_DialogSidebar.scss"; @import "./components/views/beacon/_LeftPanelLiveShareWarning.scss"; @import "./components/views/beacon/_LiveTimeRemaining.scss"; diff --git a/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss b/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss new file mode 100644 index 00000000000..bd1e9ee03e3 --- /dev/null +++ b/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss @@ -0,0 +1,55 @@ +/* +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. +*/ + +.mx_DialogOwnBeaconStatus { + position: absolute; + bottom: $spacing-32; + width: 300px; + margin-left: -150px; + left: 50%; + + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: stretch; + + background: $background; + border-radius: 8px; + box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; + + padding: 0 $spacing-12; +} + +.mx_DialogOwnBeaconStatus_avatarIcon { + flex: 0 0; + height: 32px; + width: 32px; + margin: $spacing-8 0 $spacing-8 0; +} + +.mx_DialogOwnBeaconStatus_avatar { + flex: 0 0; + box-sizing: border-box; + + border: 2px solid $location-live-color; + margin: $spacing-8 0 $spacing-8 0; +} + +.mx_DialogOwnBeaconStatus_status { + flex: 1 1; + padding-right: 0; +} \ No newline at end of file diff --git a/src/components/views/beacon/BeaconViewDialog.tsx b/src/components/views/beacon/BeaconViewDialog.tsx index 76b9b75e3e4..9dc1352f105 100644 --- a/src/components/views/beacon/BeaconViewDialog.tsx +++ b/src/components/views/beacon/BeaconViewDialog.tsx @@ -36,6 +36,7 @@ import { Icon as LocationIcon } from '../../../../res/img/element-icons/location import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import DialogSidebar from './DialogSidebar'; +import DialogOwnBeaconStatus from './DialogOwnBeaconStatus'; interface IProps extends IDialogProps { roomId: Room['roomId']; @@ -124,6 +125,7 @@ const BeaconViewDialog: React.FC = ({ { _t('View list') } } + ); diff --git a/src/components/views/beacon/DialogOwnBeaconStatus.tsx b/src/components/views/beacon/DialogOwnBeaconStatus.tsx new file mode 100644 index 00000000000..77689be8aed --- /dev/null +++ b/src/components/views/beacon/DialogOwnBeaconStatus.tsx @@ -0,0 +1,80 @@ +/* +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 { Room, Beacon } from 'matrix-js-sdk/src/matrix'; +import { LocationAssetType } from 'matrix-js-sdk/src/@types/location'; + +import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../stores/OwnBeaconStore'; +import { useEventEmitterState } from '../../../hooks/useEventEmitter'; +import { OwnProfileStore } from '../../../stores/OwnProfileStore'; +import OwnBeaconStatus from './OwnBeaconStatus'; +import { BeaconDisplayStatus } from './displayStatus'; +import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import MemberAvatar from '../avatars/MemberAvatar'; +import StyledLiveBeaconIcon from './StyledLiveBeaconIcon'; + +interface Props { + roomId: Room['roomId']; +} + +const useOwnBeacon = (roomId: Room['roomId']): Beacon | undefined => { + const ownBeacon = useEventEmitterState( + OwnProfileStore.instance, + OwnBeaconStoreEvent.LivenessChange, + () => { + const [ownBeaconId] = OwnBeaconStore.instance.getLiveBeaconIds(roomId); + return OwnBeaconStore.instance.getBeaconById(ownBeaconId); + }, + ); + + return ownBeacon; +}; + +const DialogOwnBeaconStatus: React.FC = ({ roomId }) => { + const beacon = useOwnBeacon(roomId); + + const matrixClient = useContext(MatrixClientContext); + const room = matrixClient.getRoom(beacon.roomId); + + if (!beacon?.isLive) { + return null; + } + + const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self; + const beaconMember = isSelfLocation ? + room.getMember(beacon.beaconInfoOwner) : + undefined; + + return
+ { isSelfLocation ? + : + + } + +
; +}; + +export default DialogOwnBeaconStatus; diff --git a/src/components/views/beacon/OwnBeaconStatus.tsx b/src/components/views/beacon/OwnBeaconStatus.tsx index 0a682b11641..0cd1cfb49a0 100644 --- a/src/components/views/beacon/OwnBeaconStatus.tsx +++ b/src/components/views/beacon/OwnBeaconStatus.tsx @@ -25,7 +25,9 @@ import AccessibleButton from '../elements/AccessibleButton'; interface Props { displayStatus: BeaconDisplayStatus; + className?: string; beacon?: Beacon; + withIcon?: boolean; } /** @@ -33,7 +35,7 @@ interface Props { * for errors and actions available for users own live beacons */ const OwnBeaconStatus: React.FC> = ({ - beacon, displayStatus, className, ...rest + beacon, displayStatus, ...rest }) => { const { hasWireError, @@ -49,12 +51,10 @@ const OwnBeaconStatus: React.FC> = ({ displayStatus; return { ownDisplayStatus === BeaconDisplayStatus.Active && Date: Wed, 20 Apr 2022 14:47:55 +0200 Subject: [PATCH 2/5] add tests for own beacon status Signed-off-by: Kerry Archibald --- .../views/beacon/DialogOwnBeaconStatus.tsx | 2 +- .../views/beacon/BeaconViewDialog-test.tsx | 31 +- .../BeaconViewDialog-test.tsx.snap | 310 ++++++++++++++++++ .../OwnBeaconStatus-test.tsx.snap | 13 +- 4 files changed, 342 insertions(+), 14 deletions(-) diff --git a/src/components/views/beacon/DialogOwnBeaconStatus.tsx b/src/components/views/beacon/DialogOwnBeaconStatus.tsx index 77689be8aed..6ae1e8f5b8f 100644 --- a/src/components/views/beacon/DialogOwnBeaconStatus.tsx +++ b/src/components/views/beacon/DialogOwnBeaconStatus.tsx @@ -48,7 +48,7 @@ const DialogOwnBeaconStatus: React.FC = ({ roomId }) => { const beacon = useOwnBeacon(roomId); const matrixClient = useContext(MatrixClientContext); - const room = matrixClient.getRoom(beacon.roomId); + const room = matrixClient.getRoom(roomId); if (!beacon?.isLive) { return null; diff --git a/test/components/views/beacon/BeaconViewDialog-test.tsx b/test/components/views/beacon/BeaconViewDialog-test.tsx index 7adfbf86c8b..10528a6ee05 100644 --- a/test/components/views/beacon/BeaconViewDialog-test.tsx +++ b/test/components/views/beacon/BeaconViewDialog-test.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { + BeaconEvent, MatrixClient, MatrixEvent, Room, @@ -34,6 +35,7 @@ import { makeRoomWithStateEvents, } from '../../../test-utils'; import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils'; +import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore'; describe('', () => { // 14.03.2022 16:15 @@ -50,9 +52,10 @@ describe('', () => { getClientWellKnown: jest.fn().mockReturnValue({ [TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' }, }), - getUserId: jest.fn().mockReturnValue(aliceId), + getUserId: jest.fn().mockReturnValue(bobId), getRoom: jest.fn(), isGuest: jest.fn().mockReturnValue(false), + getVisibleRooms: jest.fn().mockReturnValue([]), }); // make fresh rooms every time @@ -83,6 +86,10 @@ describe('', () => { const getComponent = (props = {}) => mount(); + beforeEach(() => { + jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockRestore(); + }); + it('renders a map with markers', () => { const room = setupRoom([defaultEvent]); const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); @@ -95,6 +102,28 @@ describe('', () => { expect(component.find('SmartMarker').length).toEqual(1); }); + it('does not render any own beacon status when user is not live sharing', () => { + // default event belongs to alice, we are bob + const room = setupRoom([defaultEvent]); + const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); + beacon.addLocations([location1]); + mockClient.emit(BeaconEvent.New, defaultEvent, beacon); + const component = getComponent(); + expect(component.find('DialogOwnBeaconStatus').html()).toBeNull(); + }); + + it('renders own beacon status when user is live sharing', () => { + // default event belongs to alice + const room = setupRoom([defaultEvent]); + const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); + beacon.addLocations([location1]); + jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockReturnValue([beacon.identifier]); + jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockReturnValue(beacon); + mockClient.emit(BeaconEvent.New, defaultEvent, beacon); + const component = getComponent(); + expect(component.find('DialogOwnBeaconStatus')).toMatchSnapshot(); + }); + it('updates markers on changes to beacons', () => { const room = setupRoom([defaultEvent]); const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); diff --git a/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap b/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap index 59e47767817..bcfaf002c5e 100644 --- a/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap @@ -35,3 +35,313 @@ exports[` renders a fallback when no live beacons remain 1`] `; + +exports[` renders own beacon status when user is live sharing 1`] = ` + +
+ + + + + + + + + + +
+
+ + Live location enabled + + + + 1h left + + +
+ +
+ Stop +
+
+
+
+
+
+
+`; diff --git a/test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap b/test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap index d34eedeb56e..d2751ba2d9d 100644 --- a/test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap @@ -5,24 +5,13 @@ exports[` renders without a beacon instance 1`] = ` displayStatus="Loading" >
- -
-
From 43b5d04f905574b3d58af73254c3c97f58ae823a Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 20 Apr 2022 14:49:48 +0200 Subject: [PATCH 3/5] stylelint Signed-off-by: Kerry Archibald --- res/css/components/views/beacon/_DialogOwnBeaconStatus.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss b/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss index bd1e9ee03e3..791e276f050 100644 --- a/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss +++ b/res/css/components/views/beacon/_DialogOwnBeaconStatus.scss @@ -52,4 +52,4 @@ limitations under the License. .mx_DialogOwnBeaconStatus_status { flex: 1 1; padding-right: 0; -} \ No newline at end of file +} From 3f47dda663b5ab936eb2e890d3152dcd6571c5c4 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 20 Apr 2022 14:53:49 +0200 Subject: [PATCH 4/5] remove huge snapshot Signed-off-by: Kerry Archibald --- .../views/beacon/BeaconViewDialog-test.tsx | 8 +- .../BeaconViewDialog-test.tsx.snap | 310 ------------------ 2 files changed, 7 insertions(+), 311 deletions(-) diff --git a/test/components/views/beacon/BeaconViewDialog-test.tsx b/test/components/views/beacon/BeaconViewDialog-test.tsx index 10528a6ee05..a08abbeeac8 100644 --- a/test/components/views/beacon/BeaconViewDialog-test.tsx +++ b/test/components/views/beacon/BeaconViewDialog-test.tsx @@ -36,6 +36,7 @@ import { } from '../../../test-utils'; import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils'; import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore'; +import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus'; describe('', () => { // 14.03.2022 16:15 @@ -117,11 +118,16 @@ describe('', () => { const room = setupRoom([defaultEvent]); const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); beacon.addLocations([location1]); + // mock own beacon store to show default event as alice's live beacon jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockReturnValue([beacon.identifier]); jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockReturnValue(beacon); mockClient.emit(BeaconEvent.New, defaultEvent, beacon); const component = getComponent(); - expect(component.find('DialogOwnBeaconStatus')).toMatchSnapshot(); + expect(component.find('MemberAvatar').length).toBeTruthy(); + expect(component.find('OwnBeaconStatus').props()).toEqual({ + beacon, displayStatus: BeaconDisplayStatus.Active, + className: 'mx_DialogOwnBeaconStatus_status', + }); }); it('updates markers on changes to beacons', () => { diff --git a/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap b/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap index bcfaf002c5e..59e47767817 100644 --- a/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/BeaconViewDialog-test.tsx.snap @@ -35,313 +35,3 @@ exports[` renders a fallback when no live beacons remain 1`]
`; - -exports[` renders own beacon status when user is live sharing 1`] = ` - -
- - - - - - - - - - -
-
- - Live location enabled - - - - 1h left - - -
- -
- Stop -
-
-
-
-
-
-
-`; From 6ea75459780ac2fb743109bd2c05311b7bac578c Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 20 Apr 2022 14:54:16 +0200 Subject: [PATCH 5/5] remove unused emits from test Signed-off-by: Kerry Archibald --- test/components/views/beacon/BeaconViewDialog-test.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/components/views/beacon/BeaconViewDialog-test.tsx b/test/components/views/beacon/BeaconViewDialog-test.tsx index a08abbeeac8..0f23036b8b1 100644 --- a/test/components/views/beacon/BeaconViewDialog-test.tsx +++ b/test/components/views/beacon/BeaconViewDialog-test.tsx @@ -18,7 +18,6 @@ import React from 'react'; import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { - BeaconEvent, MatrixClient, MatrixEvent, Room, @@ -108,7 +107,6 @@ describe('', () => { const room = setupRoom([defaultEvent]); const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); beacon.addLocations([location1]); - mockClient.emit(BeaconEvent.New, defaultEvent, beacon); const component = getComponent(); expect(component.find('DialogOwnBeaconStatus').html()).toBeNull(); }); @@ -121,7 +119,6 @@ describe('', () => { // mock own beacon store to show default event as alice's live beacon jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockReturnValue([beacon.identifier]); jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockReturnValue(beacon); - mockClient.emit(BeaconEvent.New, defaultEvent, beacon); const component = getComponent(); expect(component.find('MemberAvatar').length).toBeTruthy(); expect(component.find('OwnBeaconStatus').props()).toEqual({