diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index a5dae148f4..9e4d4dc471 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -125,8 +125,34 @@ limitations under the License. } } - .mx_UserInfo_memberDetails { - text-align: center; + .mx_UserInfo_memberDetails .mx_UserInfo_profileField { + display: flex; + justify-content: center; + align-items: center; + + margin: 6px 0; + + .mx_IconButton { + margin-left: 6px; + width: 16px; + height: 16px; + + &::before { + mask-size: 80%; + } + } + + .mx_UserInfo_roleDescription { + display: flex; + justify-content: center; + align-items: center; + // try to make it the same height as the dropdown + margin: 11px 0 12px 0; + } + + .mx_Field { + margin: 0; + } } .mx_UserInfo_field { diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 52caa69fcf..379292c152 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -27,7 +27,6 @@ import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import createRoom from '../../../createRoom'; import DMRoomMap from '../../../utils/DMRoomMap'; -import Unread from '../../../Unread'; import AccessibleButton from '../elements/AccessibleButton'; import SdkConfig from '../../../SdkConfig'; import SettingsStore from "../../../settings/SettingsStore"; @@ -40,6 +39,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg"; import E2EIcon from "../rooms/E2EIcon"; import withLegacyMatrixClient from "../../../utils/withLegacyMatrixClient"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; +import {textualPowerLevel} from '../../../Roles'; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -780,43 +780,11 @@ const useIsSynapseAdmin = (cli) => { return isAdmin; }; -// cli is injected by withLegacyMatrixClient -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]); - - // only display the devices list if our client supports E2E - const _enableDevices = cli.isCryptoEnabled(); - - // Load whether or not we are a Synapse Admin - const isSynapseAdmin = useIsSynapseAdmin(cli); - - // Check whether the user is ignored - const [isIgnored, setIsIgnored] = useState(cli.isUserIgnored(user.userId)); - // Recheck if the user or client changes - useEffect(() => { - setIsIgnored(cli.isUserIgnored(user.userId)); - }, [cli, user.userId]); - // Recheck also if we receive new accountData m.ignored_user_list - const accountDataHandler = useCallback((ev) => { - if (ev.getType() === "m.ignored_user_list") { - setIsIgnored(cli.isUserIgnored(user.userId)); - } - }, [cli, user.userId]); - useEventEmitter(cli, "accountData", accountDataHandler); - - // Count of how many operations are currently in progress, if > 0 then show a Spinner - const [pendingUpdateCount, setPendingUpdateCount] = useState(0); - const startUpdating = useCallback(() => { - setPendingUpdateCount(pendingUpdateCount + 1); - }, [pendingUpdateCount]); - const stopUpdating = useCallback(() => { - setPendingUpdateCount(pendingUpdateCount - 1); - }, [pendingUpdateCount]); - +function useRoomPermissions(cli, room, user) { const [roomPermissions, setRoomPermissions] = useState({ // modifyLevelMax is the max PL we can set this user to, typically min(their PL, our PL) && canSetPL modifyLevelMax: -1, + canAffectUser: false, canInvite: false, }); const updateRoomPermissions = useCallback(async () => { @@ -847,6 +815,7 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room setRoomPermissions({ canInvite: me.powerLevel >= powerLevels.invite, + canAffectUser, modifyLevelMax, }); }, [cli, user, room]); @@ -856,38 +825,16 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room return () => { setRoomPermissions({ maximalPowerLevel: -1, + canAffectUser: false, canInvite: false, }); }; }, [updateRoomPermissions]); - const onSynapseDeactivate = useCallback(async () => { - const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); - const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, { - title: _t("Deactivate user?"), - description: -
{ _t( - "Deactivating this user will log them out and prevent them from logging back in. Additionally, " + - "they will leave all the rooms they are in. This action cannot be reversed. Are you sure you " + - "want to deactivate this user?", - ) }
, - button: _t("Deactivate user"), - danger: true, - }); - - const [accepted] = await finished; - if (!accepted) return; - try { - cli.deactivateSynapseUser(user.userId); - } catch (err) { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createTrackedDialog('Failed to deactivate user', '', ErrorDialog, { - title: _t('Failed to deactivate user'), - description: ((err && err.message) ? err.message : _t("Operation failed")), - }); - } - }, [cli, user.userId]); + return roomPermissions; +} +const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room, startUpdating, stopUpdating, roomPermissions}) => { const onPowerChange = useCallback(async (powerLevel) => { const _applyPowerChange = (roomId, target, powerLevel, powerLevelEvent) => { startUpdating(); @@ -953,6 +900,104 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room _applyPowerChange(roomId, target, powerLevel, powerLevelEvent); }, [user.roomId, user.userId, room && room.currentState, cli]); // eslint-disable-line + + const [isEditingPL, setEditingPL] = useState(false); + if (room && user.roomId) { // is in room + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0; + const powerLevel = parseInt(user.powerLevel); + const IconButton = sdk.getComponent('elements.IconButton'); + if (isEditingPL) { + const PowerSelector = sdk.getComponent('elements.PowerSelector'); + return ( +
+ + setEditingPL(false)} /> +
+ ); + } else { + const modifyButton = roomPermissions.canAffectUser ? + ( setEditingPL(true)} />) : null; + const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); + const label = _t("%(role)s in %(roomName)s", + {role, roomName: room.name}, + {strong: label => {label}}, + ); + return (
{label}{modifyButton}
); + } + } +}); + +// cli is injected by withLegacyMatrixClient +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]); + + // only display the devices list if our client supports E2E + const _enableDevices = cli.isCryptoEnabled(); + + // Load whether or not we are a Synapse Admin + const isSynapseAdmin = useIsSynapseAdmin(cli); + + // Check whether the user is ignored + const [isIgnored, setIsIgnored] = useState(cli.isUserIgnored(user.userId)); + // Recheck if the user or client changes + useEffect(() => { + setIsIgnored(cli.isUserIgnored(user.userId)); + }, [cli, user.userId]); + // Recheck also if we receive new accountData m.ignored_user_list + const accountDataHandler = useCallback((ev) => { + if (ev.getType() === "m.ignored_user_list") { + setIsIgnored(cli.isUserIgnored(user.userId)); + } + }, [cli, user.userId]); + useEventEmitter(cli, "accountData", accountDataHandler); + + // Count of how many operations are currently in progress, if > 0 then show a Spinner + const [pendingUpdateCount, setPendingUpdateCount] = useState(0); + const startUpdating = useCallback(() => { + setPendingUpdateCount(pendingUpdateCount + 1); + }, [pendingUpdateCount]); + const stopUpdating = useCallback(() => { + setPendingUpdateCount(pendingUpdateCount - 1); + }, [pendingUpdateCount]); + + const roomPermissions = useRoomPermissions(cli, room, user); + + const onSynapseDeactivate = useCallback(async () => { + const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); + const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, { + title: _t("Deactivate user?"), + description: +
{ _t( + "Deactivating this user will log them out and prevent them from logging back in. Additionally, " + + "they will leave all the rooms they are in. This action cannot be reversed. Are you sure you " + + "want to deactivate this user?", + ) }
, + button: _t("Deactivate user"), + danger: true, + }); + + const [accepted] = await finished; + if (!accepted) return; + try { + cli.deactivateSynapseUser(user.userId); + } catch (err) { + const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); + Modal.createTrackedDialog('Failed to deactivate user', '', ErrorDialog, { + title: _t('Failed to deactivate user'), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); + } + }, [cli, user.userId]); + + const onMemberAvatarKey = e => { if (e.key === "Enter") { onMemberAvatarClick(); @@ -1058,26 +1103,6 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room statusLabel = { statusMessage }; } - let memberDetails = null; - - if (room && user.roomId) { // is in room - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0; - - const PowerSelector = sdk.getComponent('elements.PowerSelector'); - memberDetails =
-
- -
- -
; - } - const avatarUrl = user.getMxcAvatarUrl ? user.getMxcAvatarUrl() : user.avatarUrl; let avatarElement; if (avatarUrl) { @@ -1102,6 +1127,11 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room title={_t('Close')} />; } + const memberDetails = ; + const isRoomEncrypted = useIsEncrypted(cli, room); // undefined means yet to be loaded, null means failed to load, otherwise list of devices const [devices, setDevices] = useState(undefined); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a7bcf29407..46ad7d5135 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -118,6 +118,7 @@ "Restricted": "Restricted", "Moderator": "Moderator", "Admin": "Admin", + "Custom %(level)s": "Custom %(level)s", "Start a chat": "Start a chat", "Who would you like to communicate with?": "Who would you like to communicate with?", "Email, name or Matrix ID": "Email, name or Matrix ID", @@ -1080,6 +1081,7 @@ "Failed to withdraw invitation": "Failed to withdraw invitation", "Failed to remove user from community": "Failed to remove user from community", "Failed to deactivate user": "Failed to deactivate user", + "%(role)s in %(roomName)s": "%(role)s in %(roomName)s", "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.", "Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.", "Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.",