diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 9e4d4dc471..2b2add49ee 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -132,8 +132,8 @@ limitations under the License. margin: 6px 0; - .mx_IconButton { - margin-left: 6px; + .mx_IconButton, .mx_Spinner { + margin-left: 20px; width: 16px; height: 16px; @@ -148,6 +148,10 @@ limitations under the License. align-items: center; // try to make it the same height as the dropdown margin: 11px 0 12px 0; + + .mx_IconButton { + margin-left: 6px; + } } .mx_Field { diff --git a/src/Roles.js b/src/Roles.js index 4c0d2ab4e6..7cc3c880d7 100644 --- a/src/Roles.js +++ b/src/Roles.js @@ -30,6 +30,6 @@ export function textualPowerLevel(level, usersDefault) { if (LEVEL_ROLE_MAP[level]) { return LEVEL_ROLE_MAP[level]; } else { - return _t("Custom %(level)s", {level}); + return _t("Custom (%(level)s)", {level}); } } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a0a256e4c8..589eca9a08 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -837,9 +837,46 @@ function useRoomPermissions(cli, room, user) { } const PowerLevelSection = withLegacyMatrixClient(({matrixClient: cli, user, room, roomPermissions, powerLevels}) => { + const [isEditing, setEditing] = useState(false); + if (room && user.roomId) { // is in room + if (isEditing) { + return ( setEditing(false)} />); + } else { + const IconButton = sdk.getComponent('elements.IconButton'); + const powerLevelUsersDefault = powerLevels.users_default || 0; + const powerLevel = parseInt(user.powerLevel, 10); + const modifyButton = roomPermissions.canEdit ? + ( setEditing(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}
+
+ ); + } + } else { + return null; + } +}); + +const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room, roomPermissions, onFinished}) => { + const [isUpdating, setIsUpdating] = useState(false); + const [selectedPowerLevel, setSelectedPowerLevel] = useState(parseInt(user.powerLevel, 10)); + const [isDirty, setIsDirty] = useState(false); + const onPowerChange = useCallback((powerLevel) => { + setIsDirty(true); + setSelectedPowerLevel(parseInt(powerLevel, 10)); + }, [setSelectedPowerLevel, setIsDirty]); + + const changePowerLevel = useCallback(async () => { const _applyPowerChange = (roomId, target, powerLevel, powerLevelEvent) => { - startUpdating(); - cli.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then( + return cli.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then( function() { // NO-OP; rely on the m.room.member event coming down else we could // get out of sync if we force setState here! @@ -852,87 +889,86 @@ const PowerLevelSection = withLegacyMatrixClient(({matrixClient: cli, user, room description: _t("Failed to change power level"), }); }, - ).finally(() => { - stopUpdating(); - }).done(); + ); }; - const roomId = user.roomId; - const target = user.userId; - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - if (!powerLevelEvent.getContent().users) { - _applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - return; - } - - const myUserId = cli.getUserId(); - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - // If we are changing our own PL it can only ever be decreasing, which we cannot reverse. - if (myUserId === target) { - try { - if (!(await _warnSelfDemote())) return; - _applyPowerChange(roomId, target, powerLevel, powerLevelEvent); - } catch (e) { - console.error("Failed to warn about self demotion: ", e); + try { + if (!isDirty) { + return; } - return; + + setIsUpdating(true); + + const powerLevel = selectedPowerLevel; + + const roomId = user.roomId; + const target = user.userId; + + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + if (!powerLevelEvent) return; + + if (!powerLevelEvent.getContent().users) { + _applyPowerChange(roomId, target, powerLevel, powerLevelEvent); + return; + } + + const myUserId = cli.getUserId(); + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + // If we are changing our own PL it can only ever be decreasing, which we cannot reverse. + if (myUserId === target) { + try { + if (!(await _warnSelfDemote())) return; + } catch (e) { + console.error("Failed to warn about self demotion: ", e); + } + await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent); + return; + } + + const myPower = powerLevelEvent.getContent().users[myUserId]; + if (parseInt(myPower) === parseInt(powerLevel)) { + const {finished} = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, { + title: _t("Warning!"), + description: +
+ { _t("You will not be able to undo this change as you are promoting the user " + + "to have the same power level as yourself.") }
+ { _t("Are you sure?") } +
, + button: _t("Continue"), + }); + + const [confirmed] = await finished; + if (confirmed) return; + } + await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent); + } finally { + onFinished(); } + }, [user.roomId, user.userId, cli, selectedPowerLevel, isDirty, setIsUpdating, onFinished, room]); - const myPower = powerLevelEvent.getContent().users[myUserId]; - if (parseInt(myPower) === parseInt(powerLevel)) { - const {finished} = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, { - title: _t("Warning!"), - description: -
- { _t("You will not be able to undo this change as you are promoting the user " + - "to have the same power level as yourself.") }
- { _t("Are you sure?") } -
, - button: _t("Continue"), - }); + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0; + const IconButton = sdk.getComponent('elements.IconButton'); + const Spinner = sdk.getComponent("elements.Spinner"); + const buttonOrSpinner = isUpdating ? : + ; - const [confirmed] = await finished; - if (confirmed) return; - } - _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}
); - } - } + const PowerSelector = sdk.getComponent('elements.PowerSelector'); + return ( +
+ + {buttonOrSpinner} +
+ ); }); // cli is injected by withLegacyMatrixClient diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 46ad7d5135..029000e9d2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -118,7 +118,7 @@ "Restricted": "Restricted", "Moderator": "Moderator", "Admin": "Admin", - "Custom %(level)s": "Custom %(level)s", + "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,8 +1080,8 @@ "Remove this user from community?": "Remove this user from community?", "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", + "Failed to deactivate user": "Failed to deactivate user", "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.",