diff --git a/res/css/_components.scss b/res/css/_components.scss index 60cacf0383..f4b833bdd4 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 0000000000..791e276f05 --- /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; +} diff --git a/src/components/views/beacon/BeaconViewDialog.tsx b/src/components/views/beacon/BeaconViewDialog.tsx index 76b9b75e3e..9dc1352f10 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 0000000000..6ae1e8f5b8 --- /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(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 0a682b1164..0cd1cfb49a 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 && ', () => { // 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,31 @@ 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]); + 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]); + // 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); + const component = getComponent(); + 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', () => { const room = setupRoom([defaultEvent]); const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent)); diff --git a/test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap b/test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap index d34eedeb56..d2751ba2d9 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" >
- -
-