add PL edit mode, don't show selector by default
still saves when changing the selector thoughpull/21833/head
							parent
							
								
									91e02aa623
								
							
						
					
					
						commit
						6db162a3a7
					
				| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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:
 | 
			
		||||
                <div>{ _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?",
 | 
			
		||||
                ) }</div>,
 | 
			
		||||
            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 (
 | 
			
		||||
                <div className="mx_UserInfo_profileField">
 | 
			
		||||
                    <PowerSelector
 | 
			
		||||
                        label={null}
 | 
			
		||||
                        value={powerLevel}
 | 
			
		||||
                        maxValue={roomPermissions.modifyLevelMax}
 | 
			
		||||
                        usersDefault={powerLevelUsersDefault}
 | 
			
		||||
                        onChange={onPowerChange}
 | 
			
		||||
                    />
 | 
			
		||||
                    <IconButton icon="check" onClick={() => setEditingPL(false)} />
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            const modifyButton = roomPermissions.canAffectUser ?
 | 
			
		||||
                (<IconButton icon="edit" onClick={() => setEditingPL(true)} />) : null;
 | 
			
		||||
            const role = textualPowerLevel(powerLevel, powerLevelUsersDefault);
 | 
			
		||||
            const label = _t("<strong>%(role)s</strong> in %(roomName)s",
 | 
			
		||||
                {role, roomName: room.name},
 | 
			
		||||
                {strong: label => <strong>{label}</strong>},
 | 
			
		||||
            );
 | 
			
		||||
            return (<div className="mx_UserInfo_profileField"><div className="mx_UserInfo_roleDescription">{label}{modifyButton}</div></div>);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 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:
 | 
			
		||||
                <div>{ _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?",
 | 
			
		||||
                ) }</div>,
 | 
			
		||||
            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 = <span className="mx_UserInfo_statusMessage">{ statusMessage }</span>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 = <div>
 | 
			
		||||
            <div className="mx_UserInfo_profileField">
 | 
			
		||||
                <PowerSelector
 | 
			
		||||
                    value={parseInt(user.powerLevel)}
 | 
			
		||||
                    maxValue={roomPermissions.modifyLevelMax}
 | 
			
		||||
                    disabled={roomPermissions.modifyLevelMax < 0}
 | 
			
		||||
                    usersDefault={powerLevelUsersDefault}
 | 
			
		||||
                    onChange={onPowerChange} />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        </div>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 = <PowerLevelEditor
 | 
			
		||||
        user={user} room={room} roomPermissions={roomPermissions}
 | 
			
		||||
        startUpdating={startUpdating} stopUpdating={stopUpdating}
 | 
			
		||||
    />;
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
    "<strong>%(role)s</strong> in %(roomName)s": "<strong>%(role)s</strong> 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.",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue