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 ();
+ }
+ }
+});
+
+// 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.",