From 0d64066bad81cf271a715921d8569a0ce3c6ff74 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Wed, 16 Mar 2022 13:27:01 +0100 Subject: [PATCH 01/16] add simple live share warning Signed-off-by: Kerry Archibald --- .../beacon/_LeftPanelLiveShareWarning.scss | 31 ++++++++++++ res/css/structures/_LeftPanel.scss | 9 +++- res/css/structures/_MatrixChat.scss | 2 +- src/components/structures/LoggedInView.tsx | 42 +++++++++------- .../beacon/LeftPanelLiveShareWarning.tsx | 50 +++++++++++++++++++ .../beacon/LeftPanelLiveShareWarning-test.tsx | 37 ++++++++++++++ 6 files changed, 150 insertions(+), 21 deletions(-) create mode 100644 res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss create mode 100644 src/components/views/beacon/LeftPanelLiveShareWarning.tsx create mode 100644 test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx diff --git a/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss b/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss new file mode 100644 index 00000000000..0ee60a65f27 --- /dev/null +++ b/res/css/components/views/beacon/_LeftPanelLiveShareWarning.scss @@ -0,0 +1,31 @@ +/* +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_LeftPanelLiveShareWarning { + width: 100%; + box-sizing: border-box; + + padding: $spacing-4; + text-align: center; + + background-color: $accent; + color: #fff; + font-size: $font-10px; + + // panel backdrops overlay the whole sidepanel + // go above to get hover for title + z-index: 1; +} diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 1113a5d9efd..f663d5a70bb 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -23,13 +23,20 @@ $roomListCollapsedWidth: 68px; } } -.mx_LeftPanel_wrapper { +.mx_LeftPanel_outerWrapper { display: flex; + flex-direction: column; max-width: 50%; position: relative; // Contain the amount of layers rendered by constraining what actually needs re-layering via css contain: layout paint; +} + +.mx_LeftPanel_wrapper { + display: flex; + flex-direction: row; + flex: 1; .mx_LeftPanel_wrapper--user { background-color: $roomlist-bg-color; diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index a95bfa9eb9f..d0ee7d92ebd 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -63,7 +63,7 @@ limitations under the License. } /* not the left panel, and not the resize handle, so the roomview/groupview/... */ -.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(.mx_LeftPanel_wrapper) { +.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(.mx_LeftPanel_outerWrapper) { background-color: $background; flex: 1 1 0; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 4967954cdd6..2194d2f6e5d 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -75,6 +75,7 @@ import RightPanelStore from '../../stores/right-panel/RightPanelStore'; import { TimelineRenderingType } from "../../contexts/RoomContext"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload"; +import LeftPanelLiveShareWarning from '../views/beacon/LeftPanelLiveShareWarning'; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -694,8 +695,10 @@ class LoggedInView extends React.Component { >
-
- { SettingsStore.getValue('TagPanel.enableTagPanel') && +
+ +
+ { SettingsStore.getValue('TagPanel.enableTagPanel') && (
{ { SettingsStore.getValue("feature_custom_tags") ? : null }
) - } - { SpaceStore.spacesEnabled ? <> + } + { SpaceStore.spacesEnabled ? <> + + + : null } - - : null } - -
- +
+ +
diff --git a/src/components/views/beacon/LeftPanelLiveShareWarning.tsx b/src/components/views/beacon/LeftPanelLiveShareWarning.tsx new file mode 100644 index 00000000000..d0be4f087eb --- /dev/null +++ b/src/components/views/beacon/LeftPanelLiveShareWarning.tsx @@ -0,0 +1,50 @@ +/* +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 classNames from 'classnames'; +import React from 'react'; + +import { useEventEmitterState } from '../../../hooks/useEventEmitter'; +import { _t } from '../../../languageHandler'; +import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../stores/OwnBeaconStore'; +import { Icon as LiveLocationIcon } from '../../../../res/img/location/live-location.svg'; + +interface Props { + isMinimized?: boolean; +} + +const LeftPanelLiveShareWarning: React.FC = ({ isMinimized }) => { + const hasLiveBeacons = useEventEmitterState( + OwnBeaconStore.instance, + OwnBeaconStoreEvent.LivenessChange, + () => OwnBeaconStore.instance.hasLiveBeacons(), + ); + + if (!hasLiveBeacons) { + return null; + } + + return
+ { isMinimized ? : _t('You are sharing your live location') } +
; +}; + +export default LeftPanelLiveShareWarning; diff --git a/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx b/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx new file mode 100644 index 00000000000..b177c9a3175 --- /dev/null +++ b/test/components/views/beacon/LeftPanelLiveShareWarning-test.tsx @@ -0,0 +1,37 @@ +/* +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 { mount } from 'enzyme'; + +import '../../../skinned-sdk'; +import LeftPanelLiveShareWarning from '../../../../src/components/views/beacon/LeftPanelLiveShareWarning'; + +describe('', () => { + const defaultProps = {}; + const getComponent = (props = {}) => + mount(); + + it('renders correctly when not minimized', () => { + const component = getComponent(); + expect(component).toMatchSnapshot(); + }); + + it('renders correctly when minimized', () => { + const component = getComponent({ isMinimized: true }); + expect(component).toMatchSnapshot(); + }); +}); From b08bbc89d8cb5aa6008fc2c627e7f4b3e66368b3 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 15 Mar 2022 15:59:38 +0100 Subject: [PATCH 02/16] rough first cut of OwnBeaconStore Signed-off-by: Kerry Archibald --- src/components/structures/LeftPanel.tsx | 4 + src/stores/OwnBeaconStore.ts | 116 ++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/stores/OwnBeaconStore.ts diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 08f0078c134..06022892f46 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -29,6 +29,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import { replaceableComponent } from "../../utils/replaceableComponent"; import SpaceStore from "../../stores/spaces/SpaceStore"; +import OwnBeaconStore from "../../stores/OwnOwnBeaconStore"; import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; @@ -81,6 +82,9 @@ export default class LeftPanel extends React.Component { BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace); + + console.log('hhh', 'OwnBeaconStore', OwnBeaconStore); + } private static get breadcrumbsMode(): BreadcrumbsMode { diff --git a/src/stores/OwnBeaconStore.ts b/src/stores/OwnBeaconStore.ts new file mode 100644 index 00000000000..7d4ff1b3ddd --- /dev/null +++ b/src/stores/OwnBeaconStore.ts @@ -0,0 +1,116 @@ +import { RoomStateEvent, BeaconEvent, Room, Beacon } from "matrix-js-sdk/src/matrix"; + +import defaultDispatcher from "../dispatcher/dispatcher"; +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; + +const isOwnBeacon = (beacon: Beacon, userId: string): boolean => beacon.beaconInfoId === userId; + +type OwnBeaconStoreState = { + beacons: Map; + beaconsByRoomId: Map>; + roomIdsWithLiveBeacons: Room['roomId'][]; +} +export class OwnBeaconStore extends AsyncStoreWithClient { + public readonly beacons = new Map(); + public readonly beaconsByRoomId = new Map>(); + private roomIdsWithLiveBeacons = []; + private static internalInstance = new OwnBeaconStore(); + + + private constructor() { + super(defaultDispatcher); + } + + public static get instance(): OwnBeaconStore { + return OwnBeaconStore.internalInstance; + } + + protected async onNotReady() { + + await this.reset({}); + } + + protected async onReady() { + this.matrixClient.on(BeaconEvent.LivenessChange, this.onBeaconLiveness); + this.matrixClient.on(BeaconEvent.New, this.onNewBeacon); + + this.initialiseBeaconState(); + } + + protected async onAction(payload: ActionPayload): Promise { + // we don't actually do anything here + } + + + public hasLiveBeacons(roomId?: string): boolean { + if (!roomId) { + return !!this.roomIdsWithLiveBeacons.length; + } + return this.roomIdsWithLiveBeacons.includes(roomId); + } + + private onNewBeacon(beacon: Beacon): void { + console.log('hhh', 'this.onNewBeacon', beacon); + if (!isOwnBeacon(beacon, this.matrixClient.getUserId())) { + return; + } + this.addBeacon(beacon); + } + + + private onBeaconLiveness(isLive: boolean, beacon: Beacon): void { + // check if we care about this beacon + if (!this.beacons.has(beacon.beaconInfoId)) { + return; + } + + if (!isLive && this.roomIdsWithLiveBeacons.includes(beacon.beaconInfoId)) { + this.roomIdsWithLiveBeacons = this.roomIdsWithLiveBeacons.filter(beaconId => beaconId !== beacon.beaconInfoId); + // TODO emit something here? + } + + if (isLive && !this.roomIdsWithLiveBeacons.includes(beacon.beaconInfoId)) { + this.roomIdsWithLiveBeacons.push(beacon.beaconInfoId); + // TODO emit something here? + } + + console.log('hhh', 'this.onBeaconLiveness', isLive, beacon.beaconInfoId); + // TODO stop or start polling here + // if not content is live but beacon is not, update state event with live: false + } + + private initialiseBeaconState() { + const userId = this.matrixClient.getUserId(); + const visibleRooms = this.matrixClient.getVisibleRooms(); + visibleRooms + // .filter(room => room.currentState.hasLiveBeacons) + .forEach(room => { + const roomState = room.currentState; + const beacons = roomState.beacons; + + [...beacons.values()].filter(beacon => isOwnBeacon(beacon, userId)).forEach(beacon => this.addBeacon(beacon)) + + console.log('hhh', 'beacons', beacons); + }) + } + + private addBeacon(beacon: Beacon): void { + this.beacons.set(beacon.beaconInfoId, beacon); + + if (!this.beaconsByRoomId.has(beacon.roomId)) { + this.beaconsByRoomId.set(beacon.roomId, new Set()); + } + + this.beaconsByRoomId.get(beacon.roomId).add(beacon.beaconInfoId); + beacon.monitorLiveness(); + + console.log('hhh', 'addBeacon', this.beacons, this.beaconsByRoomId) + } + + private checkLiveness(): void { + + } +} + +export default OwnBeaconStore.instance; \ No newline at end of file From ad80df77b4ef6d08abaa42a98444a092ebaece3a Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Tue, 15 Mar 2022 17:20:01 +0100 Subject: [PATCH 03/16] working (?) has live beacons status Signed-off-by: Kerry Archibald --- src/components/structures/LeftPanel.tsx | 18 +++++--- src/stores/OwnBeaconStore.ts | 57 ++++++++++++++----------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 06022892f46..263c26f094a 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -29,7 +29,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import { replaceableComponent } from "../../utils/replaceableComponent"; import SpaceStore from "../../stores/spaces/SpaceStore"; -import OwnBeaconStore from "../../stores/OwnOwnBeaconStore"; +import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../stores/OwnBeaconStore"; import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; @@ -46,7 +46,6 @@ import UserMenu from "./UserMenu"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { shouldShowComponent } from "../../customisations/helpers/UIComponents"; import { UIComponent } from "../../settings/UIFeature"; - interface IProps { isMinimized: boolean; resizeNotifier: ResizeNotifier; @@ -61,6 +60,7 @@ enum BreadcrumbsMode { interface IState { showBreadcrumbs: BreadcrumbsMode; activeSpace: SpaceKey; + hasLiveBeacons: boolean; } @replaceableComponent("structures.LeftPanel") @@ -77,14 +77,14 @@ export default class LeftPanel extends React.Component { this.state = { activeSpace: SpaceStore.instance.activeSpace, showBreadcrumbs: LeftPanel.breadcrumbsMode, + hasLiveBeacons: OwnBeaconStore.instance.hasLiveBeacons(), }; BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace); - - console.log('hhh', 'OwnBeaconStore', OwnBeaconStore); - + // TODO whats wrong with ownbeaconstore that it needs bind + OwnBeaconStore.instance.on(OwnBeaconStoreEvent.LivenessChange, this.onBeaconLivenessChange.bind(this)); } private static get breadcrumbsMode(): BreadcrumbsMode { @@ -106,6 +106,7 @@ export default class LeftPanel extends React.Component { SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); UIStore.instance.stopTrackingElementDimensions("ListContainer"); UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders); + OwnBeaconStore.instance.removeListener(OwnBeaconStoreEvent.LivenessChange, this.onBeaconLivenessChange); this.listContainerRef.current?.removeEventListener("scroll", this.onScroll); } @@ -401,6 +402,10 @@ export default class LeftPanel extends React.Component { ); } + private onBeaconLivenessChange(hasLiveBeacons: boolean): void { + this.setState({ hasLiveBeacons }); + } + public render(): React.ReactNode { const roomList = { "mx_AutoHideScrollbar", ); + console.log('hhh', 'this.state', this.state); + return (
+ { this.state.hasLiveBeacons &&

has live beacons

}