diff --git a/src/app/organisms/profile-viewer/ProfileViewer.jsx b/src/app/organisms/profile-viewer/ProfileViewer.jsx index 73b722e5..b5940b4d 100644 --- a/src/app/organisms/profile-viewer/ProfileViewer.jsx +++ b/src/app/organisms/profile-viewer/ProfileViewer.jsx @@ -7,10 +7,11 @@ import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; -import { selectRoom } from '../../../client/action/navigation'; +import { selectRoom, openReusableContextMenu } from '../../../client/action/navigation'; import * as roomActions from '../../../client/action/room'; import { getUsername, getUsernameOfRoomMember, getPowerLabel } from '../../../util/matrixUtil'; +import { getEventCords } from '../../../util/common'; import colorMXID from '../../../util/colorMXID'; import Text from '../../atoms/text/Text'; @@ -18,6 +19,7 @@ import Chip from '../../atoms/chip/Chip'; import IconButton from '../../atoms/button/IconButton'; import Avatar from '../../atoms/avatar/Avatar'; import Button from '../../atoms/button/Button'; +import PowerLevelSelector from '../../molecules/power-level-selector/PowerLevelSelector'; import Dialog from '../../molecules/dialog/Dialog'; import SettingTile from '../../molecules/setting-tile/SettingTile'; @@ -25,6 +27,8 @@ import ShieldEmptyIC from '../../../../public/res/ic/outlined/shield-empty.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; +import { useForceUpdate } from '../../hooks/useForceUpdate'; + function SessionInfo({ userId }) { const [devices, setDevices] = useState(null); const mx = initMatrix.matrixClient; @@ -230,6 +234,7 @@ function ProfileViewer() { const [isOpen, setIsOpen] = useState(false); const [roomId, setRoomId] = useState(null); const [userId, setUserId] = useState(null); + const [, forceUpdate] = useForceUpdate(); const mx = initMatrix.matrixClient; const room = roomId ? mx.getRoom(roomId) : null; @@ -240,19 +245,30 @@ function ProfileViewer() { else username = getUsername(userId); } - function loadProfile(uId, rId) { - setIsOpen(true); - setUserId(uId); - setRoomId(rId); - } - useEffect(() => { + const loadProfile = (uId, rId) => { + setIsOpen(true); + setUserId(uId); + setRoomId(rId); + }; navigation.on(cons.events.navigation.PROFILE_VIEWER_OPENED, loadProfile); return () => { navigation.removeListener(cons.events.navigation.PROFILE_VIEWER_OPENED, loadProfile); }; }, []); + useEffect(() => { + const handlePowerLevelChange = (mEvent, member) => { + if (mEvent.getRoomId() === roomId && member.userId === userId) { + forceUpdate(); + } + }; + mx.on('RoomMember.powerLevel', handlePowerLevelChange); + return () => { + mx.removeListener('RoomMember.powerLevel', handlePowerLevelChange); + }; + }, [roomId, userId]); + const handleAfterClose = () => { setUserId(null); setRoomId(null); @@ -261,8 +277,40 @@ function ProfileViewer() { function renderProfile() { const member = room.getMember(userId) || mx.getUser(userId) || {}; const avatarMxc = member.getMxcAvatarUrl?.() || member.avatarUrl; + const powerLevel = member.powerLevel || 0; - const canChangeRole = room.currentState.maySendEvent('m.room.power_levels', mx.getUserId()); + const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0; + + const canChangeRole = ( + room.currentState.maySendEvent('m.room.power_levels', mx.getUserId()) + && (powerLevel < myPowerLevel || userId === mx.getUserId()) + ); + + const handleChangePowerLevel = (newPowerLevel) => { + if (newPowerLevel === powerLevel) return; + if (newPowerLevel === myPowerLevel + ? confirm('You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?') + : true + ) { + roomActions.setPowerLevel(roomId, userId, newPowerLevel); + } + }; + const handlePowerSelector = (e) => { + openReusableContextMenu( + 'bottom', + getEventCords(e, '.btn-surface'), + (closeMenu) => ( + { + closeMenu(); + handleChangePowerLevel(pl); + }} + /> + ), + ); + }; return (
@@ -279,7 +327,10 @@ function ProfileViewer() {
Role -
diff --git a/src/app/templates/client/Client.jsx b/src/app/templates/client/Client.jsx index 837724fb..3ec7f8dd 100644 --- a/src/app/templates/client/Client.jsx +++ b/src/app/templates/client/Client.jsx @@ -4,6 +4,7 @@ import './Client.scss'; import Text from '../../atoms/text/Text'; import Spinner from '../../atoms/spinner/Spinner'; import Navigation from '../../organisms/navigation/Navigation'; +import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu'; import Room from '../../organisms/room/Room'; import Windows from '../../organisms/pw/Windows'; import Dialogs from '../../organisms/pw/Dialogs'; @@ -66,6 +67,7 @@ function Client() { + ); } diff --git a/src/client/action/room.js b/src/client/action/room.js index 5fe5faac..73783ef0 100644 --- a/src/client/action/room.js +++ b/src/client/action/room.js @@ -199,6 +199,16 @@ async function kick(roomId, userId) { return result; } +async function setPowerLevel(roomId, userId, powerLevel) { + const mx = initMatrix.matrixClient; + const room = mx.getRoom(roomId); + + const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0]; + + const result = await mx.setPowerLevel(roomId, userId, powerLevel, powerlevelEvent); + return result; +} + function createSpaceShortcut(roomId) { appDispatcher.dispatch({ type: cons.actions.room.CREATE_SPACE_SHORTCUT, @@ -216,5 +226,6 @@ function deleteSpaceShortcut(roomId) { export { join, leave, create, invite, kick, + setPowerLevel, createSpaceShortcut, deleteSpaceShortcut, };