mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 11:47:23 +03:00
Convert UserInfo to Typescript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
8bf4ef5766
commit
f945155d04
3 changed files with 203 additions and 116 deletions
|
@ -23,7 +23,7 @@ import {Action} from "../../../dispatcher/actions";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import BaseAvatar from "./BaseAvatar";
|
import BaseAvatar from "./BaseAvatar";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
|
||||||
member: RoomMember;
|
member: RoomMember;
|
||||||
fallbackUserId?: string;
|
fallbackUserId?: string;
|
||||||
width: number;
|
width: number;
|
||||||
|
|
|
@ -17,20 +17,22 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react';
|
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {Group, RoomMember, User, Room} from 'matrix-js-sdk';
|
import {MatrixClient} from 'matrix-js-sdk/src/client';
|
||||||
|
import {RoomMember} from 'matrix-js-sdk/src/models/room-member';
|
||||||
|
import {User} from 'matrix-js-sdk/src/models/user';
|
||||||
|
import {Room} from 'matrix-js-sdk/src/models/room';
|
||||||
|
import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline';
|
||||||
|
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import * as sdk from '../../../index';
|
import {_t} from '../../../languageHandler';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import createRoom, {privateShouldBeEncrypted} from '../../../createRoom';
|
import createRoom, {privateShouldBeEncrypted} from '../../../createRoom';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {EventTimeline} from "matrix-js-sdk";
|
|
||||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||||
import MultiInviter from "../../../utils/MultiInviter";
|
import MultiInviter from "../../../utils/MultiInviter";
|
||||||
import GroupStore from "../../../stores/GroupStore";
|
import GroupStore from "../../../stores/GroupStore";
|
||||||
|
@ -41,13 +43,31 @@ import {textualPowerLevel} from '../../../Roles';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||||
import EncryptionPanel from "./EncryptionPanel";
|
import EncryptionPanel from "./EncryptionPanel";
|
||||||
import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
|
import {useAsyncMemo} from '../../../hooks/useAsyncMemo';
|
||||||
import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification';
|
import {legacyVerifyUser, verifyDevice, verifyUser} from '../../../verification';
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
|
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
|
||||||
import BaseCard from "./BaseCard";
|
import BaseCard from "./BaseCard";
|
||||||
|
import {E2EStatus} from "../../../utils/ShieldUtils";
|
||||||
|
import ImageView from "../elements/ImageView";
|
||||||
|
import Spinner from "../elements/Spinner";
|
||||||
|
import IconButton from "../elements/IconButton";
|
||||||
|
import PowerSelector from "../elements/PowerSelector";
|
||||||
|
import MemberAvatar from "../avatars/MemberAvatar";
|
||||||
|
import PresenceLabel from "../rooms/PresenceLabel";
|
||||||
|
import ShareDialog from "../dialogs/ShareDialog";
|
||||||
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
|
import QuestionDialog from "../dialogs/QuestionDialog";
|
||||||
|
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
|
||||||
|
import InfoDialog from "../dialogs/InfoDialog";
|
||||||
|
|
||||||
const _disambiguateDevices = (devices) => {
|
interface IDevice {
|
||||||
|
deviceId: string;
|
||||||
|
ambiguous?: boolean;
|
||||||
|
getDisplayName(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const disambiguateDevices = (devices: IDevice[]) => {
|
||||||
const names = Object.create(null);
|
const names = Object.create(null);
|
||||||
for (let i = 0; i < devices.length; i++) {
|
for (let i = 0; i < devices.length; i++) {
|
||||||
const name = devices[i].getDisplayName();
|
const name = devices[i].getDisplayName();
|
||||||
|
@ -64,11 +84,11 @@ const _disambiguateDevices = (devices) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getE2EStatus = (cli, userId, devices) => {
|
export const getE2EStatus = (cli: MatrixClient, userId: string, devices: IDevice[]): E2EStatus => {
|
||||||
const isMe = userId === cli.getUserId();
|
const isMe = userId === cli.getUserId();
|
||||||
const userTrust = cli.checkUserTrust(userId);
|
const userTrust = cli.checkUserTrust(userId);
|
||||||
if (!userTrust.isCrossSigningVerified()) {
|
if (!userTrust.isCrossSigningVerified()) {
|
||||||
return userTrust.wasCrossSigningVerified() ? "warning" : "normal";
|
return userTrust.wasCrossSigningVerified() ? E2EStatus.Warning : E2EStatus.Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
const anyDeviceUnverified = devices.some(device => {
|
const anyDeviceUnverified = devices.some(device => {
|
||||||
|
@ -81,10 +101,10 @@ export const getE2EStatus = (cli, userId, devices) => {
|
||||||
const deviceTrust = cli.checkDeviceTrust(userId, deviceId);
|
const deviceTrust = cli.checkDeviceTrust(userId, deviceId);
|
||||||
return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified();
|
return isMe ? !deviceTrust.isCrossSigningVerified() : !deviceTrust.isVerified();
|
||||||
});
|
});
|
||||||
return anyDeviceUnverified ? "warning" : "verified";
|
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function openDMForUser(matrixClient, userId) {
|
async function openDMForUser(matrixClient: MatrixClient, userId: string) {
|
||||||
const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||||
const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
|
const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
|
||||||
const room = matrixClient.getRoom(roomId);
|
const room = matrixClient.getRoom(roomId);
|
||||||
|
@ -107,6 +127,7 @@ async function openDMForUser(matrixClient, userId) {
|
||||||
|
|
||||||
const createRoomOptions = {
|
const createRoomOptions = {
|
||||||
dmUserId: userId,
|
dmUserId: userId,
|
||||||
|
encryption: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (privateShouldBeEncrypted()) {
|
if (privateShouldBeEncrypted()) {
|
||||||
|
@ -122,10 +143,12 @@ async function openDMForUser(matrixClient, userId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoom(createRoomOptions);
|
return createRoom(createRoomOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useHasCrossSigningKeys(cli, member, canVerify, setUpdating) {
|
type SetUpdating = (updating: boolean) => void;
|
||||||
|
|
||||||
|
function useHasCrossSigningKeys(cli: MatrixClient, member: RoomMember, canVerify: boolean, setUpdating: SetUpdating) {
|
||||||
return useAsyncMemo(async () => {
|
return useAsyncMemo(async () => {
|
||||||
if (!canVerify) {
|
if (!canVerify) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -142,7 +165,7 @@ function useHasCrossSigningKeys(cli, member, canVerify, setUpdating) {
|
||||||
}, [cli, member, canVerify], undefined);
|
}, [cli, member, canVerify], undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeviceItem({userId, device}) {
|
function DeviceItem({userId, device}: {userId: string, device: IDevice}) {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const isMe = userId === cli.getUserId();
|
const isMe = userId === cli.getUserId();
|
||||||
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
||||||
|
@ -169,8 +192,8 @@ function DeviceItem({userId, device}) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const deviceName = device.ambiguous ?
|
const deviceName = device.ambiguous ?
|
||||||
(device.getDisplayName() ? device.getDisplayName() : "") + " (" + device.deviceId + ")" :
|
(device.getDisplayName() ? device.getDisplayName() : "") + " (" + device.deviceId + ")" :
|
||||||
device.getDisplayName();
|
device.getDisplayName();
|
||||||
let trustedLabel = null;
|
let trustedLabel = null;
|
||||||
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
||||||
|
|
||||||
|
@ -198,8 +221,7 @@ function DeviceItem({userId, device}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function DevicesSection({devices, userId, loading}) {
|
function DevicesSection({devices, userId, loading}: {devices: IDevice[], userId: string, loading: boolean}) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const userTrust = cli.checkUserTrust(userId);
|
const userTrust = cli.checkUserTrust(userId);
|
||||||
|
|
||||||
|
@ -210,7 +232,7 @@ function DevicesSection({devices, userId, loading}) {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
if (devices === null) {
|
if (devices === null) {
|
||||||
return _t("Unable to load session list");
|
return <>{_t("Unable to load session list")}</>;
|
||||||
}
|
}
|
||||||
const isMe = userId === cli.getUserId();
|
const isMe = userId === cli.getUserId();
|
||||||
const deviceTrusts = devices.map(d => cli.checkDeviceTrust(userId, d.deviceId));
|
const deviceTrusts = devices.map(d => cli.checkDeviceTrust(userId, d.deviceId));
|
||||||
|
@ -285,7 +307,11 @@ function DevicesSection({devices, userId, loading}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
|
const UserOptionsSection: React.FC<{
|
||||||
|
member: RoomMember;
|
||||||
|
isIgnored: boolean;
|
||||||
|
canInvite: boolean;
|
||||||
|
}> = ({member, isIgnored, canInvite}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
let ignoreButton = null;
|
let ignoreButton = null;
|
||||||
|
@ -296,7 +322,6 @@ const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
|
||||||
const isMe = member.userId === cli.getUserId();
|
const isMe = member.userId === cli.getUserId();
|
||||||
|
|
||||||
const onShareUserClick = () => {
|
const onShareUserClick = () => {
|
||||||
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
|
||||||
Modal.createTrackedDialog('share room member dialog', '', ShareDialog, {
|
Modal.createTrackedDialog('share room member dialog', '', ShareDialog, {
|
||||||
target: member,
|
target: member,
|
||||||
});
|
});
|
||||||
|
@ -318,7 +343,10 @@ const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
ignoreButton = (
|
ignoreButton = (
|
||||||
<AccessibleButton onClick={onIgnoreToggle} className={classNames("mx_UserInfo_field", {mx_UserInfo_destructive: !isIgnored})}>
|
<AccessibleButton
|
||||||
|
onClick={onIgnoreToggle}
|
||||||
|
className={classNames("mx_UserInfo_field", {mx_UserInfo_destructive: !isIgnored})}
|
||||||
|
>
|
||||||
{ isIgnored ? _t("Unignore") : _t("Ignore") }
|
{ isIgnored ? _t("Unignore") : _t("Ignore") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
@ -367,7 +395,6 @@ const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
|
||||||
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
|
||||||
title: _t('Failed to invite'),
|
title: _t('Failed to invite'),
|
||||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
@ -413,8 +440,7 @@ const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const _warnSelfDemote = async () => {
|
const warnSelfDemote = async () => {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
const {finished} = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, {
|
const {finished} = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, {
|
||||||
title: _t("Demote yourself?"),
|
title: _t("Demote yourself?"),
|
||||||
description:
|
description:
|
||||||
|
@ -430,7 +456,7 @@ const _warnSelfDemote = async () => {
|
||||||
return confirmed;
|
return confirmed;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GenericAdminToolsContainer = ({children}) => {
|
const GenericAdminToolsContainer: React.FC<{}> = ({children}) => {
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserInfo_container">
|
<div className="mx_UserInfo_container">
|
||||||
<h3>{ _t("Admin Tools") }</h3>
|
<h3>{ _t("Admin Tools") }</h3>
|
||||||
|
@ -441,7 +467,20 @@ const GenericAdminToolsContainer = ({children}) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const _isMuted = (member, powerLevelContent) => {
|
interface IPowerLevelsContent {
|
||||||
|
events?: Record<string, number>;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
users_default?: number;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
events_default?: number;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
state_default?: number;
|
||||||
|
ban?: number;
|
||||||
|
kick?: number;
|
||||||
|
redact?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) => {
|
||||||
if (!powerLevelContent || !member) return false;
|
if (!powerLevelContent || !member) return false;
|
||||||
|
|
||||||
const levelToSend = (
|
const levelToSend = (
|
||||||
|
@ -451,8 +490,8 @@ const _isMuted = (member, powerLevelContent) => {
|
||||||
return member.powerLevel < levelToSend;
|
return member.powerLevel < levelToSend;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRoomPowerLevels = (cli, room) => {
|
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
|
||||||
const [powerLevels, setPowerLevels] = useState({});
|
const [powerLevels, setPowerLevels] = useState<IPowerLevelsContent>({});
|
||||||
|
|
||||||
const update = useCallback(() => {
|
const update = useCallback(() => {
|
||||||
if (!room) {
|
if (!room) {
|
||||||
|
@ -479,14 +518,19 @@ export const useRoomPowerLevels = (cli, room) => {
|
||||||
return powerLevels;
|
return powerLevels;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomKickButton = ({member, startUpdating, stopUpdating}) => {
|
interface IBaseProps {
|
||||||
|
member: RoomMember;
|
||||||
|
startUpdating(): void;
|
||||||
|
stopUpdating(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoomKickButton: React.FC<IBaseProps> = ({member, startUpdating, stopUpdating}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
// check if user can be kicked/disinvited
|
// check if user can be kicked/disinvited
|
||||||
if (member.membership !== "invite" && member.membership !== "join") return null;
|
if (member.membership !== "invite" && member.membership !== "join") return null;
|
||||||
|
|
||||||
const onKick = async () => {
|
const onKick = async () => {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
|
||||||
const {finished} = Modal.createTrackedDialog(
|
const {finished} = Modal.createTrackedDialog(
|
||||||
'Confirm User Action Dialog',
|
'Confirm User Action Dialog',
|
||||||
'onKick',
|
'onKick',
|
||||||
|
@ -509,7 +553,6 @@ const RoomKickButton = ({member, startUpdating, stopUpdating}) => {
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
console.log("Kick success");
|
console.log("Kick success");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Kick error: " + err);
|
console.error("Kick error: " + err);
|
||||||
Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, {
|
||||||
title: _t("Failed to kick"),
|
title: _t("Failed to kick"),
|
||||||
|
@ -526,7 +569,7 @@ const RoomKickButton = ({member, startUpdating, stopUpdating}) => {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RedactMessagesButton = ({member}) => {
|
const RedactMessagesButton: React.FC<IBaseProps> = ({member}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const onRedactAllMessages = async () => {
|
const onRedactAllMessages = async () => {
|
||||||
|
@ -554,7 +597,6 @@ const RedactMessagesButton = ({member}) => {
|
||||||
const user = member.name;
|
const user = member.name;
|
||||||
|
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
const InfoDialog = sdk.getComponent("dialogs.InfoDialog");
|
|
||||||
Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, {
|
Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, {
|
||||||
title: _t("No recent messages by %(user)s found", {user}),
|
title: _t("No recent messages by %(user)s found", {user}),
|
||||||
description:
|
description:
|
||||||
|
@ -563,14 +605,14 @@ const RedactMessagesButton = ({member}) => {
|
||||||
</div>,
|
</div>,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
|
|
||||||
const {finished} = Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, {
|
const {finished} = Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, {
|
||||||
title: _t("Remove recent messages by %(user)s", {user}),
|
title: _t("Remove recent messages by %(user)s", {user}),
|
||||||
description:
|
description:
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }</p>
|
<p>{ _t("You are about to remove %(count)s messages by %(user)s. " +
|
||||||
<p>{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }</p>
|
"This cannot be undone. Do you wish to continue?", {count, user}) }</p>
|
||||||
|
<p>{ _t("For a large amount of messages, this might take some time. " +
|
||||||
|
"Please don't refresh your client in the meantime.") }</p>
|
||||||
</div>,
|
</div>,
|
||||||
button: _t("Remove %(count)s messages", {count}),
|
button: _t("Remove %(count)s messages", {count}),
|
||||||
});
|
});
|
||||||
|
@ -603,11 +645,10 @@ const RedactMessagesButton = ({member}) => {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BanToggleButton = ({member, startUpdating, stopUpdating}) => {
|
const BanToggleButton: React.FC<IBaseProps> = ({member, startUpdating, stopUpdating}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const onBanOrUnban = async () => {
|
const onBanOrUnban = async () => {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
|
||||||
const {finished} = Modal.createTrackedDialog(
|
const {finished} = Modal.createTrackedDialog(
|
||||||
'Confirm User Action Dialog',
|
'Confirm User Action Dialog',
|
||||||
'onBanOrUnban',
|
'onBanOrUnban',
|
||||||
|
@ -636,7 +677,6 @@ const BanToggleButton = ({member, startUpdating, stopUpdating}) => {
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
console.log("Ban success");
|
console.log("Ban success");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Ban error: " + err);
|
console.error("Ban error: " + err);
|
||||||
Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, {
|
||||||
title: _t("Error"),
|
title: _t("Error"),
|
||||||
|
@ -661,22 +701,26 @@ const BanToggleButton = ({member, startUpdating, stopUpdating}) => {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MuteToggleButton = ({member, room, powerLevels, startUpdating, stopUpdating}) => {
|
interface IBaseRoomProps extends IBaseProps {
|
||||||
|
room: Room;
|
||||||
|
powerLevels: IPowerLevelsContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MuteToggleButton: React.FC<IBaseRoomProps> = ({member, room, powerLevels, startUpdating, stopUpdating}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
// Don't show the mute/unmute option if the user is not in the room
|
// Don't show the mute/unmute option if the user is not in the room
|
||||||
if (member.membership !== "join") return null;
|
if (member.membership !== "join") return null;
|
||||||
|
|
||||||
const isMuted = _isMuted(member, powerLevels);
|
const muted = isMuted(member, powerLevels);
|
||||||
const onMuteToggle = async () => {
|
const onMuteToggle = async () => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
const roomId = member.roomId;
|
const roomId = member.roomId;
|
||||||
const target = member.userId;
|
const target = member.userId;
|
||||||
|
|
||||||
// if muting self, warn as it may be irreversible
|
// if muting self, warn as it may be irreversible
|
||||||
if (target === cli.getUserId()) {
|
if (target === cli.getUserId()) {
|
||||||
try {
|
try {
|
||||||
if (!(await _warnSelfDemote())) return;
|
if (!(await warnSelfDemote())) return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to warn about self demotion: ", e);
|
console.error("Failed to warn about self demotion: ", e);
|
||||||
return;
|
return;
|
||||||
|
@ -692,7 +736,7 @@ const MuteToggleButton = ({member, room, powerLevels, startUpdating, stopUpdatin
|
||||||
powerLevels.events_default
|
powerLevels.events_default
|
||||||
);
|
);
|
||||||
let level;
|
let level;
|
||||||
if (isMuted) { // unmute
|
if (muted) { // unmute
|
||||||
level = levelToSend;
|
level = levelToSend;
|
||||||
} else { // mute
|
} else { // mute
|
||||||
level = levelToSend - 1;
|
level = levelToSend - 1;
|
||||||
|
@ -718,16 +762,23 @@ const MuteToggleButton = ({member, room, powerLevels, startUpdating, stopUpdatin
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = classNames("mx_UserInfo_field", {
|
const classes = classNames("mx_UserInfo_field", {
|
||||||
mx_UserInfo_destructive: !isMuted,
|
mx_UserInfo_destructive: !muted,
|
||||||
});
|
});
|
||||||
|
|
||||||
const muteLabel = isMuted ? _t("Unmute") : _t("Mute");
|
const muteLabel = muted ? _t("Unmute") : _t("Mute");
|
||||||
return <AccessibleButton className={classes} onClick={onMuteToggle}>
|
return <AccessibleButton className={classes} onClick={onMuteToggle}>
|
||||||
{ muteLabel }
|
{ muteLabel }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomAdminToolsContainer = ({room, children, member, startUpdating, stopUpdating, powerLevels}) => {
|
const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
|
||||||
|
room,
|
||||||
|
children,
|
||||||
|
member,
|
||||||
|
startUpdating,
|
||||||
|
stopUpdating,
|
||||||
|
powerLevels,
|
||||||
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
let kickButton;
|
let kickButton;
|
||||||
let banButton;
|
let banButton;
|
||||||
|
@ -786,7 +837,18 @@ const RoomAdminToolsContainer = ({room, children, member, startUpdating, stopUpd
|
||||||
return <div />;
|
return <div />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating, stopUpdating}) => {
|
interface GroupMember {
|
||||||
|
userId: string;
|
||||||
|
displayname?: string; // XXX: GroupMember objects are inconsistent :((
|
||||||
|
avatarUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GroupAdminToolsSection: React.FC<{
|
||||||
|
groupId: string;
|
||||||
|
groupMember: GroupMember;
|
||||||
|
startUpdating(): void;
|
||||||
|
stopUpdating(): void;
|
||||||
|
}> = ({children, groupId, groupMember, startUpdating, stopUpdating}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const [isPrivileged, setIsPrivileged] = useState(false);
|
const [isPrivileged, setIsPrivileged] = useState(false);
|
||||||
|
@ -814,8 +876,7 @@ const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating,
|
||||||
}, [groupId, groupMember.userId]);
|
}, [groupId, groupMember.userId]);
|
||||||
|
|
||||||
if (isPrivileged) {
|
if (isPrivileged) {
|
||||||
const _onKick = async () => {
|
const onKick = async () => {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
|
||||||
const {finished} = Modal.createDialog(ConfirmUserActionDialog, {
|
const {finished} = Modal.createDialog(ConfirmUserActionDialog, {
|
||||||
matrixClient: cli,
|
matrixClient: cli,
|
||||||
groupMember,
|
groupMember,
|
||||||
|
@ -836,7 +897,6 @@ const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating,
|
||||||
member: null,
|
member: null,
|
||||||
});
|
});
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: isInvited ?
|
description: isInvited ?
|
||||||
|
@ -850,7 +910,7 @@ const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating,
|
||||||
};
|
};
|
||||||
|
|
||||||
const kickButton = (
|
const kickButton = (
|
||||||
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={_onKick}>
|
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={onKick}>
|
||||||
{ isInvited ? _t('Disinvite') : _t('Remove from community') }
|
{ isInvited ? _t('Disinvite') : _t('Remove from community') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
@ -870,13 +930,7 @@ const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating,
|
||||||
return <div />;
|
return <div />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GroupMember = PropTypes.shape({
|
const useIsSynapseAdmin = (cli: MatrixClient) => {
|
||||||
userId: PropTypes.string.isRequired,
|
|
||||||
displayname: PropTypes.string, // XXX: GroupMember objects are inconsistent :((
|
|
||||||
avatarUrl: PropTypes.string,
|
|
||||||
});
|
|
||||||
|
|
||||||
const useIsSynapseAdmin = (cli) => {
|
|
||||||
const [isAdmin, setIsAdmin] = useState(false);
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cli.isSynapseAdministrator().then((isAdmin) => {
|
cli.isSynapseAdministrator().then((isAdmin) => {
|
||||||
|
@ -888,14 +942,20 @@ const useIsSynapseAdmin = (cli) => {
|
||||||
return isAdmin;
|
return isAdmin;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useHomeserverSupportsCrossSigning = (cli) => {
|
const useHomeserverSupportsCrossSigning = (cli: MatrixClient) => {
|
||||||
return useAsyncMemo(async () => {
|
return useAsyncMemo<boolean>(async () => {
|
||||||
return cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
return cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing");
|
||||||
}, [cli], false);
|
}, [cli], false);
|
||||||
};
|
};
|
||||||
|
|
||||||
function useRoomPermissions(cli, room, user) {
|
interface IRoomPermissions {
|
||||||
const [roomPermissions, setRoomPermissions] = useState({
|
modifyLevelMax: number;
|
||||||
|
canEdit: boolean;
|
||||||
|
canInvite: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useRoomPermissions(cli: MatrixClient, room: Room, user: User): IRoomPermissions {
|
||||||
|
const [roomPermissions, setRoomPermissions] = useState<IRoomPermissions>({
|
||||||
// modifyLevelMax is the max PL we can set this user to, typically min(their PL, our PL) && canSetPL
|
// modifyLevelMax is the max PL we can set this user to, typically min(their PL, our PL) && canSetPL
|
||||||
modifyLevelMax: -1,
|
modifyLevelMax: -1,
|
||||||
canEdit: false,
|
canEdit: false,
|
||||||
|
@ -940,7 +1000,7 @@ function useRoomPermissions(cli, room, user) {
|
||||||
updateRoomPermissions();
|
updateRoomPermissions();
|
||||||
return () => {
|
return () => {
|
||||||
setRoomPermissions({
|
setRoomPermissions({
|
||||||
maximalPowerLevel: -1,
|
modifyLevelMax: -1,
|
||||||
canEdit: false,
|
canEdit: false,
|
||||||
canInvite: false,
|
canInvite: false,
|
||||||
});
|
});
|
||||||
|
@ -950,14 +1010,18 @@ function useRoomPermissions(cli, room, user) {
|
||||||
return roomPermissions;
|
return roomPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => {
|
const PowerLevelSection: React.FC<{
|
||||||
|
user: User;
|
||||||
|
room: Room;
|
||||||
|
roomPermissions: IRoomPermissions;
|
||||||
|
powerLevels: IPowerLevelsContent;
|
||||||
|
}> = ({user, room, roomPermissions, powerLevels}) => {
|
||||||
const [isEditing, setEditing] = useState(false);
|
const [isEditing, setEditing] = useState(false);
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
return (<PowerLevelEditor
|
return (<PowerLevelEditor
|
||||||
user={user} room={room} roomPermissions={roomPermissions}
|
user={user} room={room} roomPermissions={roomPermissions}
|
||||||
onFinished={() => setEditing(false)} />);
|
onFinished={() => setEditing(false)} />);
|
||||||
} else {
|
} else {
|
||||||
const IconButton = sdk.getComponent('elements.IconButton');
|
|
||||||
const powerLevelUsersDefault = powerLevels.users_default || 0;
|
const powerLevelUsersDefault = powerLevels.users_default || 0;
|
||||||
const powerLevel = parseInt(user.powerLevel, 10);
|
const powerLevel = parseInt(user.powerLevel, 10);
|
||||||
const modifyButton = roomPermissions.canEdit ?
|
const modifyButton = roomPermissions.canEdit ?
|
||||||
|
@ -975,7 +1039,12 @@ const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
const PowerLevelEditor: React.FC<{
|
||||||
|
user: User;
|
||||||
|
room: Room;
|
||||||
|
roomPermissions: IRoomPermissions;
|
||||||
|
onFinished(): void;
|
||||||
|
}> = ({user, room, roomPermissions, onFinished}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
@ -994,7 +1063,6 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
console.log("Power change success");
|
console.log("Power change success");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to change power level " + err);
|
console.error("Failed to change power level " + err);
|
||||||
Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, {
|
||||||
title: _t("Error"),
|
title: _t("Error"),
|
||||||
|
@ -1025,12 +1093,10 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const myUserId = cli.getUserId();
|
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 we are changing our own PL it can only ever be decreasing, which we cannot reverse.
|
||||||
if (myUserId === target) {
|
if (myUserId === target) {
|
||||||
try {
|
try {
|
||||||
if (!(await _warnSelfDemote())) return;
|
if (!(await warnSelfDemote())) return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to warn about self demotion: ", e);
|
console.error("Failed to warn about self demotion: ", e);
|
||||||
}
|
}
|
||||||
|
@ -1039,7 +1105,7 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const myPower = powerLevelEvent.getContent().users[myUserId];
|
const myPower = powerLevelEvent.getContent().users[myUserId];
|
||||||
if (parseInt(myPower) === parseInt(powerLevel)) {
|
if (parseInt(myPower) === powerLevel) {
|
||||||
const {finished} = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
|
const {finished} = Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
|
||||||
title: _t("Warning!"),
|
title: _t("Warning!"),
|
||||||
description:
|
description:
|
||||||
|
@ -1062,12 +1128,9 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
||||||
|
|
||||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
|
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
|
||||||
const IconButton = sdk.getComponent('elements.IconButton');
|
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
|
||||||
const buttonOrSpinner = isUpdating ? <Spinner w={16} h={16} /> :
|
const buttonOrSpinner = isUpdating ? <Spinner w={16} h={16} /> :
|
||||||
<IconButton icon="check" onClick={changePowerLevel} />;
|
<IconButton icon="check" onClick={changePowerLevel} />;
|
||||||
|
|
||||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserInfo_profileField">
|
<div className="mx_UserInfo_profileField">
|
||||||
<PowerSelector
|
<PowerSelector
|
||||||
|
@ -1083,7 +1146,7 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDevices = (userId) => {
|
export const useDevices = (userId: string) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
// undefined means yet to be loaded, null means failed to load, otherwise list of devices
|
// undefined means yet to be loaded, null means failed to load, otherwise list of devices
|
||||||
|
@ -1094,7 +1157,7 @@ export const useDevices = (userId) => {
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
async function _downloadDeviceList() {
|
async function downloadDeviceList() {
|
||||||
try {
|
try {
|
||||||
await cli.downloadKeys([userId], true);
|
await cli.downloadKeys([userId], true);
|
||||||
const devices = cli.getStoredDevicesForUser(userId);
|
const devices = cli.getStoredDevicesForUser(userId);
|
||||||
|
@ -1104,13 +1167,13 @@ export const useDevices = (userId) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_disambiguateDevices(devices);
|
disambiguateDevices(devices);
|
||||||
setDevices(devices);
|
setDevices(devices);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setDevices(null);
|
setDevices(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_downloadDeviceList();
|
downloadDeviceList();
|
||||||
|
|
||||||
// Handle being unmounted
|
// Handle being unmounted
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1153,7 +1216,13 @@ export const useDevices = (userId) => {
|
||||||
return devices;
|
return devices;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
const BasicUserInfo: React.FC<{
|
||||||
|
room: Room;
|
||||||
|
member: User | RoomMember;
|
||||||
|
groupId: string;
|
||||||
|
devices: IDevice[];
|
||||||
|
isRoomEncrypted: boolean;
|
||||||
|
}> = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const powerLevels = useRoomPowerLevels(cli, room);
|
const powerLevels = useRoomPowerLevels(cli, room);
|
||||||
|
@ -1186,7 +1255,6 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
const roomPermissions = useRoomPermissions(cli, room, member);
|
const roomPermissions = useRoomPermissions(cli, room, member);
|
||||||
|
|
||||||
const onSynapseDeactivate = useCallback(async () => {
|
const onSynapseDeactivate = useCallback(async () => {
|
||||||
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
|
|
||||||
const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, {
|
const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, {
|
||||||
title: _t("Deactivate user?"),
|
title: _t("Deactivate user?"),
|
||||||
description:
|
description:
|
||||||
|
@ -1207,7 +1275,6 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
console.error("Failed to deactivate user");
|
console.error("Failed to deactivate user");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
|
||||||
Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
|
||||||
title: _t('Failed to deactivate user'),
|
title: _t('Failed to deactivate user'),
|
||||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
@ -1260,8 +1327,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingUpdateCount > 0) {
|
if (pendingUpdateCount > 0) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
spinner = <Spinner imgClassName="mx_ContextualMenu_spinner" />;
|
||||||
spinner = <Loader imgClassName="mx_ContextualMenu_spinner" />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let memberDetails;
|
let memberDetails;
|
||||||
|
@ -1324,7 +1390,6 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
// HACK: only show a spinner if the device section spinner is not shown,
|
// HACK: only show a spinner if the device section spinner is not shown,
|
||||||
// to avoid showing a double spinner
|
// to avoid showing a double spinner
|
||||||
// We should ask for a design that includes all the different loading states here
|
// We should ask for a design that includes all the different loading states here
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
|
||||||
verifyButton = <Spinner />;
|
verifyButton = <Spinner />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1351,7 +1416,6 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
|
|
||||||
{ securitySection }
|
{ securitySection }
|
||||||
<UserOptionsSection
|
<UserOptionsSection
|
||||||
devices={devices}
|
|
||||||
canInvite={roomPermissions.canInvite}
|
canInvite={roomPermissions.canInvite}
|
||||||
isIgnored={isIgnored}
|
isIgnored={isIgnored}
|
||||||
member={member} />
|
member={member} />
|
||||||
|
@ -1362,7 +1426,12 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserInfoHeader = ({member, e2eStatus}) => {
|
type Member = User | RoomMember | GroupMember;
|
||||||
|
|
||||||
|
const UserInfoHeader: React.FC<{
|
||||||
|
member: Member;
|
||||||
|
e2eStatus: E2EStatus;
|
||||||
|
}> = ({member, e2eStatus}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const onMemberAvatarClick = useCallback(() => {
|
const onMemberAvatarClick = useCallback(() => {
|
||||||
|
@ -1370,7 +1439,6 @@ const UserInfoHeader = ({member, e2eStatus}) => {
|
||||||
if (!avatarUrl) return;
|
if (!avatarUrl) return;
|
||||||
|
|
||||||
const httpUrl = cli.mxcUrlToHttp(avatarUrl);
|
const httpUrl = cli.mxcUrlToHttp(avatarUrl);
|
||||||
const ImageView = sdk.getComponent("elements.ImageView");
|
|
||||||
const params = {
|
const params = {
|
||||||
src: httpUrl,
|
src: httpUrl,
|
||||||
name: member.name,
|
name: member.name,
|
||||||
|
@ -1379,7 +1447,6 @@ const UserInfoHeader = ({member, e2eStatus}) => {
|
||||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
||||||
}, [cli, member]);
|
}, [cli, member]);
|
||||||
|
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
|
||||||
const avatarElement = (
|
const avatarElement = (
|
||||||
<div className="mx_UserInfo_avatar">
|
<div className="mx_UserInfo_avatar">
|
||||||
<div>
|
<div>
|
||||||
|
@ -1421,10 +1488,13 @@ const UserInfoHeader = ({member, e2eStatus}) => {
|
||||||
|
|
||||||
let presenceLabel = null;
|
let presenceLabel = null;
|
||||||
if (showPresence) {
|
if (showPresence) {
|
||||||
const PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
presenceLabel = (
|
||||||
presenceLabel = <PresenceLabel activeAgo={presenceLastActiveAgo}
|
<PresenceLabel
|
||||||
currentlyActive={presenceCurrentlyActive}
|
activeAgo={presenceLastActiveAgo}
|
||||||
presenceState={presenceState} />;
|
currentlyActive={presenceCurrentlyActive}
|
||||||
|
presenceState={presenceState}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusLabel = null;
|
let statusLabel = null;
|
||||||
|
@ -1461,7 +1531,32 @@ const UserInfoHeader = ({member, e2eStatus}) => {
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
|
interface IProps {
|
||||||
|
user: Member;
|
||||||
|
groupId?: string;
|
||||||
|
room?: Room;
|
||||||
|
phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo;
|
||||||
|
onClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPropsWithEncryptionPanel extends React.ComponentProps<typeof EncryptionPanel> {
|
||||||
|
user: Member;
|
||||||
|
groupId: void;
|
||||||
|
room: Room;
|
||||||
|
phase: RightPanelPhases.EncryptionPanel;
|
||||||
|
onClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = IProps | IPropsWithEncryptionPanel;
|
||||||
|
|
||||||
|
const UserInfo: React.FC<Props> = ({
|
||||||
|
user,
|
||||||
|
groupId,
|
||||||
|
room,
|
||||||
|
onClose,
|
||||||
|
phase = RightPanelPhases.RoomMemberInfo,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
|
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
|
||||||
|
@ -1485,7 +1580,7 @@ const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemb
|
||||||
<BasicUserInfo
|
<BasicUserInfo
|
||||||
room={room}
|
room={room}
|
||||||
member={member}
|
member={member}
|
||||||
groupId={groupId}
|
groupId={groupId as string}
|
||||||
devices={devices}
|
devices={devices}
|
||||||
isRoomEncrypted={isRoomEncrypted} />
|
isRoomEncrypted={isRoomEncrypted} />
|
||||||
);
|
);
|
||||||
|
@ -1493,7 +1588,12 @@ const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemb
|
||||||
case RightPanelPhases.EncryptionPanel:
|
case RightPanelPhases.EncryptionPanel:
|
||||||
classes.push("mx_UserInfo_smallAvatar");
|
classes.push("mx_UserInfo_smallAvatar");
|
||||||
content = (
|
content = (
|
||||||
<EncryptionPanel {...props} member={member} onClose={onClose} isRoomEncrypted={isRoomEncrypted} />
|
<EncryptionPanel
|
||||||
|
{...props as React.ComponentProps<typeof EncryptionPanel>}
|
||||||
|
member={member}
|
||||||
|
onClose={onClose}
|
||||||
|
isRoomEncrypted={isRoomEncrypted}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1510,17 +1610,4 @@ const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemb
|
||||||
</BaseCard>;
|
</BaseCard>;
|
||||||
};
|
};
|
||||||
|
|
||||||
UserInfo.propTypes = {
|
|
||||||
user: PropTypes.oneOfType([
|
|
||||||
PropTypes.instanceOf(User),
|
|
||||||
PropTypes.instanceOf(RoomMember),
|
|
||||||
GroupMember,
|
|
||||||
]).isRequired,
|
|
||||||
group: PropTypes.instanceOf(Group),
|
|
||||||
groupId: PropTypes.string,
|
|
||||||
room: PropTypes.instanceOf(Room),
|
|
||||||
|
|
||||||
onClose: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserInfo;
|
export default UserInfo;
|
|
@ -18,8 +18,8 @@ import {useState, useEffect, DependencyList} from 'react';
|
||||||
|
|
||||||
type Fn<T> = () => Promise<T>;
|
type Fn<T> = () => Promise<T>;
|
||||||
|
|
||||||
export const useAsyncMemo = <T>(fn: Fn<T>, deps: DependencyList, initialValue?: T) => {
|
export const useAsyncMemo = <T>(fn: Fn<T>, deps: DependencyList, initialValue?: T): T => {
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState<T>(initialValue);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fn().then(setValue);
|
fn().then(setValue);
|
||||||
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
|
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
Loading…
Reference in a new issue