diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 9d0ef7cf15..207bf29998 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -20,7 +20,7 @@ limitations under the License. import React, {useCallback, useMemo, useState, useEffect} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import {Group, MatrixClient, RoomMember, User} from 'matrix-js-sdk'; +import {Group, RoomMember, User} from 'matrix-js-sdk'; import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import sdk from '../../../index'; @@ -38,6 +38,7 @@ import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; import MatrixClientPeg from "../../../MatrixClientPeg"; import E2EIcon from "../rooms/E2EIcon"; +import withLegacyMatrixClient from "../../../utils/withLegacyMatrixClient"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; const _disambiguateDevices = (devices) => { @@ -57,22 +58,12 @@ const _disambiguateDevices = (devices) => { } }; -const withLegacyMatrixClient = (Component) => class extends React.PureComponent { - static contextTypes = { - matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, - }; - - render() { - return ; - } -}; - const _getE2EStatus = (devices) => { const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); return hasUnverifiedDevice ? "warning" : "verified"; }; -const DevicesSection = withLegacyMatrixClient(({devices, userId, loading}) => { +const DevicesSection = ({devices, userId, loading}) => { const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); const Spinner = sdk.getComponent("elements.Spinner"); @@ -95,7 +86,7 @@ const DevicesSection = withLegacyMatrixClient(({devices, userId, loading}) => { ); -}); +}; const onRoomTileClick = (roomId) => { dis.dispatch({ @@ -104,7 +95,7 @@ const onRoomTileClick = (roomId) => { }); }; -const DirectChatsSection = withLegacyMatrixClient(({cli, userId, startUpdating, stopUpdating}) => { +const DirectChatsSection = withLegacyMatrixClient(({matrixClient: cli, userId, startUpdating, stopUpdating}) => { const onNewDMClick = async () => { startUpdating(); await createRoom({dmUserId: userId}); @@ -194,7 +185,7 @@ const DirectChatsSection = withLegacyMatrixClient(({cli, userId, startUpdating, ); }); -const UserOptionsSection = withLegacyMatrixClient(({cli, member, isIgnored, canInvite}) => { +const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, isIgnored, canInvite}) => { let ignoreButton = null; let insertPillButton = null; let inviteUserButton = null; @@ -371,194 +362,175 @@ const useRoomPowerLevels = (room) => { return powerLevels; }; -const RoomAdminToolsContainer = withLegacyMatrixClient(({cli, room, children, member, startUpdating, stopUpdating}) => { - let kickButton; - let banButton; - let muteButton; - let redactButton; - - const powerLevels = useRoomPowerLevels(room); - const editPowerLevel = ( - (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || - powerLevels.state_default - ); - - const me = room.getMember(cli.getUserId()); - const isMe = me.userId === member.userId; - const canAffectUser = member.powerLevel < me.powerLevel || isMe; - const membership = member.membership; - - if (canAffectUser && me.powerLevel >= powerLevels.kick) { - const onKick = async () => { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - const {finished} = Modal.createTrackedDialog( - 'Confirm User Action Dialog', - 'onKick', - ConfirmUserActionDialog, - { - member, - action: membership === "invite" ? _t("Disinvite") : _t("Kick"), - title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), - askReason: membership === "join", - danger: true, - }, - ); - - const [proceed, reason] = await finished; - if (!proceed) return; - - startUpdating(); - cli.kick(member.roomId, member.userId, reason || undefined).then(() => { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Kick success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Kick error: " + err); - Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { - title: _t("Failed to kick"), - description: ((err && err.message) ? err.message : "Operation failed"), - }); - }).finally(() => { - stopUpdating(); - }); - }; - - const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); - kickButton = ( - - { kickLabel } - +const RoomKickButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => { + const onKick = async () => { + const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); + const {finished} = Modal.createTrackedDialog( + 'Confirm User Action Dialog', + 'onKick', + ConfirmUserActionDialog, + { + member, + action: member.membership === "invite" ? _t("Disinvite") : _t("Kick"), + title: member.membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), + askReason: member.membership === "join", + danger: true, + }, ); - } - if (me.powerLevel >= powerLevels.redact) { - const onRedactAllMessages = async () => { - const {roomId, userId} = member; - const room = cli.getRoom(roomId); - if (!room) { + + const [proceed, reason] = await finished; + if (!proceed) return; + + startUpdating(); + cli.kick(member.roomId, member.userId, reason || undefined).then(() => { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Kick success"); + }, function(err) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Kick error: " + err); + Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { + title: _t("Failed to kick"), + description: ((err && err.message) ? err.message : "Operation failed"), + }); + }).finally(() => { + stopUpdating(); + }); + }; + + const kickLabel = member.membership === "invite" ? _t("Disinvite") : _t("Kick"); + return + { kickLabel } + ; +}); + +const RedactMessagesButton = withLegacyMatrixClient(({matrixClient: cli, member}) => { + const onRedactAllMessages = async () => { + const {roomId, userId} = member; + const room = cli.getRoom(roomId); + if (!room) { + return; + } + let timeline = room.getLiveTimeline(); + let eventsToRedact = []; + while (timeline) { + eventsToRedact = timeline.getEvents().reduce((events, event) => { + if (event.getSender() === userId && !event.isRedacted()) { + return events.concat(event); + } else { + return events; + } + }, eventsToRedact); + timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS); + } + + const count = eventsToRedact.length; + const user = member.name; + + if (count === 0) { + const InfoDialog = sdk.getComponent("dialogs.InfoDialog"); + Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, { + title: _t("No recent messages by %(user)s found", {user}), + description: +
+

{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }

+
, + }); + } else { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + const {finished} = Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, { + title: _t("Remove recent messages by %(user)s", {user}), + description: +
+

{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }

+

{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }

+
, + button: _t("Remove %(count)s messages", {count}), + }); + + const [confirmed] = await finished; + if (!confirmed) { return; } - let timeline = room.getLiveTimeline(); - let eventsToRedact = []; - while (timeline) { - eventsToRedact = timeline.getEvents().reduce((events, event) => { - if (event.getSender() === userId && !event.isRedacted()) { - return events.concat(event); - } else { - return events; - } - }, eventsToRedact); - timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS); - } - const count = eventsToRedact.length; - const user = member.name; + // Submitting a large number of redactions freezes the UI, + // so first yield to allow to rerender after closing the dialog. + await Promise.resolve(); - if (count === 0) { - const InfoDialog = sdk.getComponent("dialogs.InfoDialog"); - Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, { - title: _t("No recent messages by %(user)s found", {user}), - description: -
-

{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }

-
, - }); - } else { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - const {finished} = Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, { - title: _t("Remove recent messages by %(user)s", {user}), - description: -
-

{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }

-

{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }

-
, - button: _t("Remove %(count)s messages", {count}), - }); - - const [confirmed] = await finished; - if (!confirmed) { - return; + console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`); + await Promise.all(eventsToRedact.map(async event => { + try { + await cli.redactEvent(roomId, event.getId()); + } catch (err) { + // log and swallow errors + console.error("Could not redact", event.getId()); + console.error(err); } - - // Submitting a large number of redactions freezes the UI, - // so first yield to allow to rerender after closing the dialog. - await Promise.resolve(); - - console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`); - await Promise.all(eventsToRedact.map(async event => { - try { - await cli.redactEvent(roomId, event.getId()); - } catch (err) { - // log and swallow errors - console.error("Could not redact", event.getId()); - console.error(err); - } - })); - console.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`); - } - }; - - redactButton = ( - - { _t("Remove recent messages") } - - ); - } - if (canAffectUser && me.powerLevel >= powerLevels.ban) { - const onBanOrUnban = async () => { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - const {finished} = Modal.createTrackedDialog( - 'Confirm User Action Dialog', - 'onBanOrUnban', - ConfirmUserActionDialog, - { - member, - action: membership === 'ban' ? _t("Unban") : _t("Ban"), - title: membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"), - askReason: membership !== 'ban', - danger: membership !== 'ban', - }, - ); - - const [proceed, reason] = await finished; - if (!proceed) return; - - startUpdating(); - let promise; - if (membership === 'ban') { - promise = cli.unban(member.roomId, member.userId); - } else { - promise = cli.ban(member.roomId, member.userId, reason || undefined); - } - promise.then(() => { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Ban success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Ban error: " + err); - Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to ban user"), - }); - }).finally(() => { - stopUpdating(); - }); - }; - - let label = _t("Ban"); - if (membership === 'ban') { - label = _t("Unban"); + })); + console.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`); } - banButton = ( - - { label } - + }; + + return + { _t("Remove recent messages") } + ; +}); + +const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => { + const onBanOrUnban = async () => { + const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); + const {finished} = Modal.createTrackedDialog( + 'Confirm User Action Dialog', + 'onBanOrUnban', + ConfirmUserActionDialog, + { + member, + action: member.membership === 'ban' ? _t("Unban") : _t("Ban"), + title: member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"), + askReason: member.membership !== 'ban', + danger: member.membership !== 'ban', + }, ); + + const [proceed, reason] = await finished; + if (!proceed) return; + + startUpdating(); + let promise; + if (member.membership === 'ban') { + promise = cli.unban(member.roomId, member.userId); + } else { + promise = cli.ban(member.roomId, member.userId, reason || undefined); + } + promise.then(() => { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Ban success"); + }, function(err) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Ban error: " + err); + Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { + title: _t("Error"), + description: _t("Failed to ban user"), + }); + }).finally(() => { + stopUpdating(); + }); + }; + + let label = _t("Ban"); + if (member.membership === 'ban') { + label = _t("Unban"); } - if (canAffectUser && me.powerLevel >= editPowerLevel) { + + return + { label } + ; +}); + +const MuteToggleButton = withLegacyMatrixClient( + ({matrixClient: cli, member, room, powerLevels, startUpdating, stopUpdating}) => { const isMuted = _isMuted(member, powerLevels); const onMuteToggle = async () => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -610,29 +582,68 @@ const RoomAdminToolsContainer = withLegacyMatrixClient(({cli, room, children, me }; const muteLabel = isMuted ? _t("Unmute") : _t("Mute"); - muteButton = ( - - { muteLabel } - + return + { muteLabel } + ; + }, +); + +const RoomAdminToolsContainer = withLegacyMatrixClient( + ({matrixClient: cli, room, children, member, startUpdating, stopUpdating}) => { + let kickButton; + let banButton; + let muteButton; + let redactButton; + + const powerLevels = useRoomPowerLevels(room); + const editPowerLevel = ( + (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || + powerLevels.state_default ); - } - if (kickButton || banButton || muteButton || redactButton || children) { - return - { muteButton } - { kickButton } - { banButton } - { redactButton } - { children } - ; - } + const me = room.getMember(cli.getUserId()); + const isMe = me.userId === member.userId; + const canAffectUser = member.powerLevel < me.powerLevel || isMe; - return
; -}); + if (canAffectUser && me.powerLevel >= powerLevels.kick) { + kickButton = ; + } + if (me.powerLevel >= powerLevels.redact) { + redactButton = ( + + ); + } + if (canAffectUser && me.powerLevel >= powerLevels.ban) { + banButton = ; + } + if (canAffectUser && me.powerLevel >= editPowerLevel) { + muteButton = ( + + ); + } + + if (kickButton || banButton || muteButton || redactButton || children) { + return + { muteButton } + { kickButton } + { banButton } + { redactButton } + { children } + ; + } + + return
; + }, +); const GroupAdminToolsSection = withLegacyMatrixClient( - ({cli, children, groupId, groupMember, startUpdating, stopUpdating}) => { + ({matrixClient: cli, children, groupId, groupMember, startUpdating, stopUpdating}) => { const [isPrivileged, setIsPrivileged] = useState(false); const [isInvited, setIsInvited] = useState(false); @@ -734,7 +745,7 @@ const useIsSynapseAdmin = (cli) => { }; // cli is injected by withLegacyMatrixClient -const UserInfo = withLegacyMatrixClient(({cli, user, groupId, roomId, onClose}) => { +const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, roomId, onClose}) => { // Load room if we are given a room id and memoize it const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]); diff --git a/src/utils/withLegacyMatrixClient.js b/src/utils/withLegacyMatrixClient.js new file mode 100644 index 0000000000..af6a930a88 --- /dev/null +++ b/src/utils/withLegacyMatrixClient.js @@ -0,0 +1,31 @@ +/* +Copyright 2019 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 PropTypes from "prop-types"; +import {MatrixClient} from "matrix-js-sdk"; + +// Higher Order Component to allow use of legacy MatrixClient React Context +// in Functional Components which do not otherwise support legacy React Contexts +export default (Component) => class extends React.PureComponent { + static contextTypes = { + matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, + }; + + render() { + return ; + } +};