import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './ProfileViewer.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/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';
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';
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;
useEffect(() => {
let isUnmounted = false;
async function loadDevices() {
try {
await mx.downloadKeys([userId], true);
const myDevices = mx.getStoredDevicesForUser(userId);
if (isUnmounted) return;
setDevices(myDevices);
} catch {
setDevices([]);
}
}
loadDevices();
return () => {
isUnmounted = true;
};
}, [userId]);
function renderSessionChips() {
return (
{devices === null && Loading sessions...}
{devices?.length === 0 && No session found.}
{devices !== null && (devices.map((device) => (
)))}
);
}
return (
);
}
SessionInfo.propTypes = {
userId: PropTypes.string.isRequired,
};
function ProfileFooter({ roomId, userId, onRequestClose }) {
const [isCreatingDM, setIsCreatingDM] = useState(false);
const [isIgnoring, setIsIgnoring] = useState(false);
const [isUserIgnored, setIsUserIgnored] = useState(initMatrix.matrixClient.isUserIgnored(userId));
const isMountedRef = useRef(true);
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const member = room.getMember(userId);
const isInvitable = member?.membership !== 'join' && member?.membership !== 'ban';
const [isInviting, setIsInviting] = useState(false);
const [isInvited, setIsInvited] = useState(member?.membership === 'invite');
const myPowerlevel = room.getMember(mx.getUserId()).powerLevel;
const userPL = room.getMember(userId)?.powerLevel || 0;
const canIKick = room.currentState.hasSufficientPowerLevelFor('kick', myPowerlevel) && userPL < myPowerlevel;
const onCreated = (dmRoomId) => {
if (isMountedRef.current === false) return;
setIsCreatingDM(false);
selectRoom(dmRoomId);
onRequestClose();
};
useEffect(() => {
const { roomList } = initMatrix;
roomList.on(cons.events.roomList.ROOM_CREATED, onCreated);
return () => {
isMountedRef.current = false;
roomList.removeListener(cons.events.roomList.ROOM_CREATED, onCreated);
};
}, []);
useEffect(() => {
setIsUserIgnored(initMatrix.matrixClient.isUserIgnored(userId));
setIsIgnoring(false);
setIsInviting(false);
}, [userId]);
async function openDM() {
const directIds = [...initMatrix.roomList.directs];
// Check and open if user already have a DM with userId.
for (let i = 0; i < directIds.length; i += 1) {
const dRoom = mx.getRoom(directIds[i]);
const roomMembers = dRoom.getMembers();
if (roomMembers.length <= 2 && dRoom.getMember(userId)) {
selectRoom(directIds[i]);
onRequestClose();
return;
}
}
// Create new DM
try {
setIsCreatingDM(true);
await roomActions.create({
isEncrypted: true,
isDirect: true,
invite: [userId],
});
} catch {
if (isMountedRef.current === false) return;
setIsCreatingDM(false);
}
}
async function toggleIgnore() {
const ignoredUsers = mx.getIgnoredUsers();
const uIndex = ignoredUsers.indexOf(userId);
if (uIndex >= 0) {
if (uIndex === -1) return;
ignoredUsers.splice(uIndex, 1);
} else ignoredUsers.push(userId);
try {
setIsIgnoring(true);
await mx.setIgnoredUsers(ignoredUsers);
if (isMountedRef.current === false) return;
setIsUserIgnored(uIndex < 0);
setIsIgnoring(false);
} catch {
setIsIgnoring(false);
}
}
async function toggleInvite() {
try {
setIsInviting(true);
let isInviteSent = false;
if (isInvited) await roomActions.kick(roomId, userId);
else {
await roomActions.invite(roomId, userId);
isInviteSent = true;
}
if (isMountedRef.current === false) return;
setIsInvited(isInviteSent);
setIsInviting(false);
} catch {
setIsInviting(false);
}
}
return (
{ member?.membership === 'join' && }
{ (isInvited ? canIKick : room.canInvite(mx.getUserId())) && isInvitable && (
)}
);
}
ProfileFooter.propTypes = {
roomId: PropTypes.string.isRequired,
userId: PropTypes.string.isRequired,
onRequestClose: PropTypes.func.isRequired,
};
function useToggleDialog() {
const [isOpen, setIsOpen] = useState(false);
const [roomId, setRoomId] = useState(null);
const [userId, setUserId] = useState(null);
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);
};
}, []);
const closeDialog = () => setIsOpen(false);
const afterClose = () => {
setUserId(null);
setRoomId(null);
};
return [isOpen, roomId, userId, closeDialog, afterClose];
}
function useRerenderOnRoleChange(roomId, userId) {
const mx = initMatrix.matrixClient;
const [, forceUpdate] = useForceUpdate();
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]);
}
function ProfileViewer() {
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
useRerenderOnRoleChange(roomId, userId);
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
let [roomMember, username] = [null, null];
if (roomId) {
roomMember = room.getMember(userId);
username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(userId);
}
const renderProfile = () => {
const avatarMxc = roomMember.getMxcAvatarUrl?.() || mx.getMember(userId).avatarUrl;
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
const powerLevel = roomMember.powerLevel || 0;
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 (
{twemojify(username)}
{twemojify(userId)}
Role
{ userId !== mx.getUserId() && (
)}
);
};
return (
}
>
{roomId ? renderProfile() : }
);
}
export default ProfileViewer;