diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 84340d8219..1a361e7b55 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -28,6 +28,7 @@ import SettingsStore from "../settings/SettingsStore"; import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; +import RightPanelStore from "../stores/RightPanelStore"; declare global { interface Window { @@ -49,6 +50,7 @@ declare global { singletonModalManager: ModalManager; mxSettingsStore: SettingsStore; mxNotifier: typeof Notifier; + mxRightPanelStore: RightPanelStore; } interface Document { diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 24fee80c2a..11416b29fb 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -21,6 +21,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import {Room} from "matrix-js-sdk/src/models/room"; + import * as sdk from '../../index'; import dis from '../../dispatcher/dispatcher'; import RateLimitedFunc from '../../ratelimitedfunc'; @@ -34,7 +36,7 @@ import {Action} from "../../dispatcher/actions"; export default class RightPanel extends React.Component { static get propTypes() { return { - roomId: PropTypes.string, // if showing panels for a given room, this is set + room: PropTypes.instanceOf(Room), // if showing panels for a given room, this is set groupId: PropTypes.string, // if showing panels for a given group, this is set user: PropTypes.object, // used if we know the user ahead of opening the panel }; @@ -161,13 +163,13 @@ export default class RightPanel extends React.Component { } onRoomStateMember(ev, state, member) { - if (member.roomId !== this.props.roomId) { + if (member.roomId !== this.props.room.roomId) { return; } // redraw the badge on the membership list - if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.roomId) { + if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) { this._delayedUpdate(); - } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.roomId && + } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId && member.userId === this.state.member.userId) { // refresh the member info (e.g. new power level) this._delayedUpdate(); @@ -226,8 +228,8 @@ export default class RightPanel extends React.Component { switch (this.state.phase) { case RightPanelPhases.RoomMemberList: - if (this.props.roomId) { - panel = ; + if (this.props.room.roomId) { + panel = ; } break; case RightPanelPhases.GroupMemberList: @@ -242,8 +244,8 @@ export default class RightPanel extends React.Component { case RightPanelPhases.EncryptionPanel: panel = ; break; case RightPanelPhases.Room3pidMemberInfo: - panel = ; + panel = ; break; case RightPanelPhases.GroupMemberInfo: panel = ; break; case RightPanelPhases.FilePanel: - panel = ; + panel = ; break; } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index e1a075f770..ed2e5645e9 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -2062,7 +2062,7 @@ export default class RoomView extends React.Component { const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel - ? + ? : null; const timelineClasses = classNames("mx_RoomView_timeline", { diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index ef21b5d787..4e3e00f221 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -94,7 +94,7 @@ export default class ScrollPanel extends React.Component { /* startAtBottom: if set to true, the view is assumed to start * scrolled to the bottom. - * XXX: It's likley this is unecessary and can be derived from + * XXX: It's likely this is unnecessary and can be derived from * stickyBottom, but I'm adding an extra parameter to ensure * behaviour stays the same for other uses of ScrollPanel. * If so, let's remove this parameter down the line. @@ -138,6 +138,7 @@ export default class ScrollPanel extends React.Component { /* style: styles to add to the top-level div */ style: PropTypes.object, + /* resizeNotifier: ResizeNotifier to know when middle column has changed size */ resizeNotifier: PropTypes.object, diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 1d23d85b0f..8fd51d3715 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -16,23 +16,24 @@ limitations under the License. */ import React from 'react'; +import {RoomMember} from "matrix-js-sdk/src/models/room-member"; + import dis from "../../../dispatcher/dispatcher"; import {Action} from "../../../dispatcher/actions"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import BaseAvatar from "./BaseAvatar"; interface IProps { - // TODO: replace with correct type - member: any; - fallbackUserId: string; + member: RoomMember; + fallbackUserId?: string; width: number; height: number; - resizeMethod: string; + resizeMethod?: string; // The onClick to give the avatar - onClick: React.MouseEventHandler; + onClick?: React.MouseEventHandler; // Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser` - viewUserOnClick: boolean; - title: string; + viewUserOnClick?: boolean; + title?: string; } interface IState { diff --git a/src/components/views/right_panel/GroupHeaderButtons.tsx b/src/components/views/right_panel/GroupHeaderButtons.tsx index 44237e401f..dd4a82e645 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.tsx +++ b/src/components/views/right_panel/GroupHeaderButtons.tsx @@ -41,13 +41,9 @@ interface IProps {} export default class GroupHeaderButtons extends HeaderButtons { constructor(props: IProps) { super(props, HeaderKind.Group); - this.onMembersClicked = this.onMembersClicked.bind(this); - this.onRoomsClicked = this.onRoomsClicked.bind(this); } protected onAction(payload: ActionPayload) { - super.onAction(payload); - if (payload.action === Action.ViewUser) { if ((payload as ViewUserPayload).member) { this.setPhase(RightPanelPhases.RoomMemberInfo, {member: payload.member}); @@ -70,7 +66,7 @@ export default class GroupHeaderButtons extends HeaderButtons { } } - private onMembersClicked() { + private onMembersClicked = () => { if (this.state.phase === RightPanelPhases.GroupMemberInfo) { // send the active phase to trigger a toggle this.setPhase(RightPanelPhases.GroupMemberInfo); @@ -78,12 +74,12 @@ export default class GroupHeaderButtons extends HeaderButtons { // This toggles for us, if needed this.setPhase(RightPanelPhases.GroupMemberList); } - } + }; - private onRoomsClicked() { + private onRoomsClicked = () => { // This toggles for us, if needed this.setPhase(RightPanelPhases.GroupRoomList); - } + }; renderButtons() { return [ diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index bbb783ccb9..e922959bbb 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -65,9 +65,7 @@ export default abstract class HeaderButtons extends React.Component) { dis.dispatch({ diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 7ac547f499..7d732b8ae3 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -36,13 +36,9 @@ const MEMBER_PHASES = [ export default class RoomHeaderButtons extends HeaderButtons { constructor(props) { super(props, HeaderKind.Room); - this.onMembersClicked = this.onMembersClicked.bind(this); - this.onFilesClicked = this.onFilesClicked.bind(this); - this.onNotificationsClicked = this.onNotificationsClicked.bind(this); } protected onAction(payload: ActionPayload) { - super.onAction(payload); if (payload.action === Action.ViewUser) { if (payload.member) { this.setPhase(RightPanelPhases.RoomMemberInfo, {member: payload.member}); @@ -58,7 +54,7 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - private onMembersClicked() { + private onMembersClicked = () => { if (this.state.phase === RightPanelPhases.RoomMemberInfo) { // send the active phase to trigger a toggle // XXX: we should pass refireParams here but then it won't collapse as we desire it to @@ -67,17 +63,17 @@ export default class RoomHeaderButtons extends HeaderButtons { // This toggles for us, if needed this.setPhase(RightPanelPhases.RoomMemberList); } - } + }; - private onFilesClicked() { + private onFilesClicked = () => { // This toggles for us, if needed this.setPhase(RightPanelPhases.FilePanel); - } + }; - private onNotificationsClicked() { + private onNotificationsClicked = () => { // This toggles for us, if needed this.setPhase(RightPanelPhases.NotificationPanel); - } + }; public renderButtons() { return [ diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 71ee86a1ea..518bb133ce 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -45,6 +45,7 @@ import EncryptionPanel from "./EncryptionPanel"; import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; import {Action} from "../../../dispatcher/actions"; +import {useIsEncrypted} from "../../../hooks/useIsEncrypted"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -124,18 +125,6 @@ async function openDMForUser(matrixClient, userId) { createRoom(createRoomOptions); } -function useIsEncrypted(cli, room) { - const [isEncrypted, setIsEncrypted] = useState(room ? cli.isRoomEncrypted(room.roomId) : undefined); - - const update = useCallback((event) => { - if (event.getType() === "m.room.encryption") { - setIsEncrypted(cli.isRoomEncrypted(room.roomId)); - } - }, [cli, room]); - useEventEmitter(room ? room.currentState : undefined, "RoomState.events", update); - return isEncrypted; -} - function useHasCrossSigningKeys(cli, member, canVerify, setUpdating) { return useAsyncMemo(async () => { if (!canVerify) { @@ -1485,7 +1474,7 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { const cli = useContext(MatrixClientContext); - // Load room if we are given a room id and memoize it + // Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]); // fetch latest room member if we have a room, so we don't show historical information, falling back to user const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]); diff --git a/src/hooks/useIsEncrypted.ts b/src/hooks/useIsEncrypted.ts new file mode 100644 index 0000000000..79f3e539cd --- /dev/null +++ b/src/hooks/useIsEncrypted.ts @@ -0,0 +1,36 @@ +/* +Copyright 2020 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 {useCallback, useState} from "react"; +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import {useEventEmitter} from "./useEventEmitter"; + +// Hook to simplify watching whether a Matrix room is encrypted, returns undefined if room is undefined +export function useIsEncrypted(cli: MatrixClient, room?: Room): boolean | undefined { + const [isEncrypted, setIsEncrypted] = useState(room ? cli.isRoomEncrypted(room.roomId) : undefined); + + const update = useCallback((event: MatrixEvent) => { + if (room && event.getType() === "m.room.encryption") { + setIsEncrypted(cli.isRoomEncrypted(room.roomId)); + } + }, [cli, room]); + useEventEmitter(room ? room.currentState : undefined, "RoomState.events", update); + + return isEncrypted; +} diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts index c1799978ad..34445d007b 100644 --- a/src/stores/RightPanelStore.ts +++ b/src/stores/RightPanelStore.ts @@ -223,3 +223,5 @@ export default class RightPanelStore extends Store { return RightPanelStore.instance; } } + +window.mxRightPanelStore = RightPanelStore.getSharedInstance();