Skip to content

Mvvm split user info, create powerlevels component #30005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

import React, { useContext, useEffect, useState } from "react";

Check failure on line 2 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

Copyright heading is required

Check failure on line 2 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

There should be no empty line within import group

Check failure on line 2 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

'/home/runner/work/element-web/element-web/node_modules/react/index.js' imported multiple times

import { useCallback } from "react";

Check failure on line 4 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

There should be at least one empty line between import groups

Check failure on line 4 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

'/home/runner/work/element-web/element-web/node_modules/react/index.js' imported multiple times
import MatrixClientContext from "../../../../contexts/MatrixClientContext";

Check failure on line 5 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

There should be at least one empty line between import groups
import { logger } from "@sentry/browser";

Check failure on line 6 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

There should be at least one empty line between import groups

Check failure on line 6 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

`@sentry/browser` import should occur before import of `../../../../contexts/MatrixClientContext`
import { _t } from "../../../../languageHandler";
import Modal from "../../../../Modal";
import ErrorDialog from "../../../views/dialogs/ErrorDialog";
import QuestionDialog from "../../../views/dialogs/QuestionDialog";
import { warnSelfDemote } from "../../../views/right_panel/UserInfo";

Check failure on line 11 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

There should be at least one empty line between import groups
import { type RoomMember, type Room } from "matrix-js-sdk/src/matrix";

Check failure on line 12 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / ESLint

`matrix-js-sdk/src/matrix` import should occur before import of `../../../../contexts/MatrixClientContext`


export interface UserInfoPowerLevelState {
powerLevelUsersDefault: number;
selectedPowerLevel: number;
onPowerChange: (powerLevel: number) => void;
}

export const useUserInfoAdminPowerlevelViewModel = (user: RoomMember, room: Room): UserInfoPowerLevelState => {
const [selectedPowerLevel, setSelectedPowerLevel] = useState(user.powerLevel);

useEffect(() => {
setSelectedPowerLevel(user.powerLevel);
}, [user]);

const cli = useContext(MatrixClientContext);
const onPowerChange = useCallback(
async (powerLevel: number) => {
setSelectedPowerLevel(powerLevel);

const applyPowerChange = (roomId: string, target: string, powerLevel: number): Promise<unknown> => {
return cli.setPowerLevel(roomId, target, powerLevel).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!
logger.info("Power change success");
},
function (err) {
logger.error("Failed to change power level " + err);
Modal.createDialog(ErrorDialog, {
title: _t("common|error"),
description: _t("error|update_power_level"),
});
},
);
};

const roomId = user.roomId;
const target = user.userId;

const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent) return;

const myUserId = cli.getUserId();
const myPower = powerLevelEvent.getContent().users[myUserId || ""];
if (myPower && parseInt(myPower) <= powerLevel && myUserId !== target) {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("common|warning"),
description: (
<div>
{_t("user_info|promote_warning")}
<br />
{_t("common|are_you_sure")}
</div>
),
button: _t("action|continue"),
});

const [confirmed] = await finished;
if (!confirmed) return;
} else if (myUserId === target && myPower && parseInt(myPower) > powerLevel) {
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
try {
if (!(await warnSelfDemote(room?.isSpaceRoom()))) return;
} catch (e) {
logger.error("Failed to warn about self demotion: ", e);

Check failure on line 78 in src/components/viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel.tsx

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Argument of type 'unknown' is not assignable to parameter of type 'Record<string, unknown> | undefined'.
}
}

await applyPowerChange(roomId, target, powerLevel);
},
[user.roomId, user.userId, cli, room],
);

const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;

return {
powerLevelUsersDefault,
onPowerChange,
selectedPowerLevel,
};
};
112 changes: 2 additions & 110 deletions src/components/views/right_panel/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import { type ButtonEvent } from "../elements/AccessibleButton";
import SdkConfig from "../../../SdkConfig";
import MultiInviter from "../../../utils/MultiInviter";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import { textualPowerLevel } from "../../../Roles";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import EncryptionPanel from "./EncryptionPanel";
Expand All @@ -58,7 +57,6 @@ import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
import BaseCard from "./BaseCard";
import ImageView from "../elements/ImageView";
import Spinner from "../elements/Spinner";
import PowerSelector from "../elements/PowerSelector";
import MemberAvatar from "../avatars/MemberAvatar";
import PresenceLabel from "../rooms/PresenceLabel";
import BulkRedactDialog from "../dialogs/BulkRedactDialog";
Expand All @@ -83,6 +81,7 @@ import { SdkContextClass } from "../../../contexts/SDKContext";
import { Flex } from "../../utils/Flex";
import CopyableText from "../elements/CopyableText";
import { useUserTimezone } from "../../../hooks/useUserTimezone";
import { PowerLevelSection } from "./user_info/UserInfoAdminPowerLevels";

export interface IDevice extends Device {
ambiguous?: boolean;
Expand Down Expand Up @@ -890,7 +889,7 @@ const useHomeserverSupportsCrossSigning = (cli: MatrixClient): boolean => {
);
};

interface IRoomPermissions {
export interface IRoomPermissions {
modifyLevelMax: number;
canEdit: boolean;
canInvite: boolean;
Expand Down Expand Up @@ -945,112 +944,6 @@ function useRoomPermissions(cli: MatrixClient, room: Room, user: RoomMember): IR
return roomPermissions;
}

const PowerLevelSection: React.FC<{
user: RoomMember;
room: Room;
roomPermissions: IRoomPermissions;
powerLevels: IPowerLevelsContent;
}> = ({ user, room, roomPermissions, powerLevels }) => {
if (roomPermissions.canEdit) {
return <PowerLevelEditor user={user} room={room} roomPermissions={roomPermissions} />;
} else {
const powerLevelUsersDefault = powerLevels.users_default || 0;
const powerLevel = user.powerLevel;
const role = textualPowerLevel(powerLevel, powerLevelUsersDefault);
return (
<div className="mx_UserInfo_profileField">
<div className="mx_UserInfo_roleDescription">{role}</div>
</div>
);
}
};

export const PowerLevelEditor: React.FC<{
user: RoomMember;
room: Room;
roomPermissions: IRoomPermissions;
}> = ({ user, room, roomPermissions }) => {
const cli = useContext(MatrixClientContext);

const [selectedPowerLevel, setSelectedPowerLevel] = useState(user.powerLevel);
useEffect(() => {
setSelectedPowerLevel(user.powerLevel);
}, [user]);

const onPowerChange = useCallback(
async (powerLevel: number) => {
setSelectedPowerLevel(powerLevel);

const applyPowerChange = (roomId: string, target: string, powerLevel: number): Promise<unknown> => {
return cli.setPowerLevel(roomId, target, powerLevel).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!
logger.log("Power change success");
},
function (err) {
logger.error("Failed to change power level " + err);
Modal.createDialog(ErrorDialog, {
title: _t("common|error"),
description: _t("error|update_power_level"),
});
},
);
};

const roomId = user.roomId;
const target = user.userId;

const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent) return;

const myUserId = cli.getUserId();
const myPower = powerLevelEvent.getContent().users[myUserId || ""];
if (myPower && parseInt(myPower) <= powerLevel && myUserId !== target) {
const { finished } = Modal.createDialog(QuestionDialog, {
title: _t("common|warning"),
description: (
<div>
{_t("user_info|promote_warning")}
<br />
{_t("common|are_you_sure")}
</div>
),
button: _t("action|continue"),
});

const [confirmed] = await finished;
if (!confirmed) return;
} else if (myUserId === target && myPower && parseInt(myPower) > powerLevel) {
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
try {
if (!(await warnSelfDemote(room?.isSpaceRoom()))) return;
} catch (e) {
logger.error("Failed to warn about self demotion: ", e);
}
}

await applyPowerChange(roomId, target, powerLevel);
},
[user.roomId, user.userId, cli, room],
);

const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;

return (
<div className="mx_UserInfo_profileField">
<PowerSelector
label={undefined}
value={selectedPowerLevel}
maxValue={roomPermissions.modifyLevelMax}
usersDefault={powerLevelUsersDefault}
onChange={onPowerChange}
/>
</div>
);
};

async function getUserDeviceInfo(
userId: string,
cli: MatrixClient,
Expand Down Expand Up @@ -1274,7 +1167,6 @@ const BasicUserInfo: React.FC<{
if (!DMRoomMap.shared().getUserIdForRoomId((member as RoomMember).roomId)) {
memberDetails = (
<PowerLevelSection
powerLevels={powerLevels}
user={member as RoomMember}
room={room}
roomPermissions={roomPermissions}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { type RoomMember, type Room } from "matrix-js-sdk/src/matrix";
import { textualPowerLevel } from "../../../../Roles";
import PowerSelector from "../../elements/PowerSelector";
import { type IRoomPermissions } from "../UserInfo";
import { UserInfoPowerLevelState, useUserInfoAdminPowerlevelViewModel } from "../../../viewmodels/right_panel/admin/UserInfoAdminPowerlevelViewModel";

export const PowerLevelSection: React.FC<{
user: RoomMember;
room: Room;
roomPermissions: IRoomPermissions;
}> = ({ user, room, roomPermissions }) => {
const vm = useUserInfoAdminPowerlevelViewModel(user, room);

if (roomPermissions.canEdit) {
return <PowerLevelEditor vm={vm} roomPermissions={roomPermissions} />;
}

const powerLevel = user.powerLevel;
const role = textualPowerLevel(powerLevel, vm.powerLevelUsersDefault);
return (
<div className="mx_UserInfo_profileField">
<div className="mx_UserInfo_roleDescription">{role}</div>
</div>
);
};

const PowerLevelEditor: React.FC<{
vm: UserInfoPowerLevelState;
roomPermissions: IRoomPermissions;
}> = ({ vm, roomPermissions }) => {
return (
<div className="mx_UserInfo_profileField">
<PowerSelector
label={undefined}
value={vm.selectedPowerLevel}
maxValue={roomPermissions.modifyLevelMax}
usersDefault={vm.powerLevelUsersDefault}
onChange={vm.onPowerChange}
/>
</div>
);
};
Loading
Loading