Phase 1, split out UserInfo into a generic Pane, use for EncInfo
This commit is contained in:
parent
0078c2f099
commit
210616c737
6 changed files with 191 additions and 133 deletions
|
@ -49,12 +49,17 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_UserInfo_container {
|
||||
padding: 0 16px 16px 16px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.mx_UserInfo_separator {
|
||||
border-bottom: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.mx_UserInfo_memberDetailsContainer {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_nameContainer {
|
||||
|
@ -204,10 +209,9 @@ limitations under the License.
|
|||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.mx_UserInfo_scrollContainer .mx_UserInfo_container {
|
||||
.mx_UserInfo_scrollContainer:not(.mx_UserInfo_separator) {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
|
||||
> :not(h3) {
|
||||
margin-left: 8px;
|
||||
|
@ -264,3 +268,10 @@ limitations under the License.
|
|||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_UserInfo.mx_UserInfo_smallAvatar {
|
||||
.mx_UserInfo_avatar > div {
|
||||
max-width: 72px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,7 +238,18 @@ export default class RightPanel extends React.Component {
|
|||
} else if (this.state.phase === RIGHT_PANEL_PHASES.FilePanel) {
|
||||
panel = <FilePanel roomId={this.props.roomId} resizeNotifier={this.props.resizeNotifier} />;
|
||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel) {
|
||||
panel = <EncryptionPanel member={this.state.member} verificationRequest={this.state.verificationRequest} />;
|
||||
const onClose = () => {
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
member: this.state.member,
|
||||
});
|
||||
};
|
||||
panel = (
|
||||
<EncryptionPanel
|
||||
member={this.state.member}
|
||||
verificationRequest={this.state.verificationRequest}
|
||||
onClose={onClose} />
|
||||
);
|
||||
}
|
||||
|
||||
const classes = classNames("mx_RightPanel", "mx_fadable", {
|
||||
|
|
|
@ -21,11 +21,17 @@ import {_t} from "../../../languageHandler";
|
|||
export default class EncryptionInfo extends React.PureComponent {
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return (<div className="mx_UserInfo"><div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify User")}</h3>
|
||||
<p>{_t("For extra security, verify this user by checking a one-time code on both of your devices.")}</p>
|
||||
<p>{_t("For maximum security, do this in person.")}</p>
|
||||
<AccessibleButton kind="primary" onClick={this.props.onStartVerification}>{_t("Start Verification")}</AccessibleButton>
|
||||
</div></div>);
|
||||
return (
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify User")}</h3>
|
||||
<div>
|
||||
<p>{_t("For extra security, verify this user by checking a one-time code on both of your devices.")}</p>
|
||||
<p>{_t("For maximum security, do this in person.")}</p>
|
||||
<AccessibleButton kind="primary" className="mx_UserInfo_verify" onClick={this.props.onStartVerification}>
|
||||
{_t("Start Verification")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -19,6 +19,8 @@ import EncryptionInfo from "./EncryptionInfo";
|
|||
import VerificationPanel from "./VerificationPanel";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {ensureDMExists} from "../../../createRoom";
|
||||
import {UserInfoPane} from "./UserInfo";
|
||||
import {_t} from "../../../languageHandler";
|
||||
|
||||
export default class EncryptionPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -27,15 +29,30 @@ export default class EncryptionPanel extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
const request = this.props.verificationRequest || this.state.verificationRequest;
|
||||
const {member} = this.props;
|
||||
if (request) {
|
||||
return <VerificationPanel request={request} key={request.channel.transactionId} />;
|
||||
content = <VerificationPanel request={request} key={request.channel.transactionId} />;
|
||||
} else if (member) {
|
||||
return <EncryptionInfo onStartVerification={this._onStartVerification} member={member} />;
|
||||
content = <EncryptionInfo onStartVerification={this._onStartVerification} member={member} />;
|
||||
} else {
|
||||
return <p>Not a member nor request, not sure what to render</p>;
|
||||
content = <p>Not a member nor request, not sure what to render</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<UserInfoPane className="mx_UserInfo_smallAvatar" member={member} onClose={this.props.onClose} e2eStatus="">
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{_t("Encryption")}</h3>
|
||||
<div>
|
||||
<p>{_t("Messages in this room are end-to-end encrypted.")}</p>
|
||||
<p>{_t("Your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ content }
|
||||
</UserInfoPane>
|
||||
);
|
||||
}
|
||||
|
||||
_onStartVerification = async () => {
|
||||
|
|
|
@ -59,7 +59,7 @@ const _disambiguateDevices = (devices) => {
|
|||
}
|
||||
};
|
||||
|
||||
const _getE2EStatus = (cli, userId, devices) => {
|
||||
export const getE2EStatus = (cli, userId, devices) => {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
const hasUnverifiedDevice = devices.some((device) => device.isUnverified());
|
||||
return hasUnverifiedDevice ? "warning" : "verified";
|
||||
|
@ -1047,6 +1047,117 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const UserInfoPane = ({children, className, onClose, e2eStatus, member}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
let closeButton;
|
||||
if (onClose) {
|
||||
closeButton = <AccessibleButton className="mx_UserInfo_cancel" onClick={onClose} title={_t('Close')}>
|
||||
<div />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
let presenceState;
|
||||
let presenceLastActiveAgo;
|
||||
let presenceCurrentlyActive;
|
||||
let statusMessage;
|
||||
|
||||
if (member instanceof RoomMember && member.user) {
|
||||
presenceState = member.user.presence;
|
||||
presenceLastActiveAgo = member.user.lastActiveAgo;
|
||||
presenceCurrentlyActive = member.user.currentlyActive;
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
statusMessage = member.user._unstable_statusMessage;
|
||||
}
|
||||
}
|
||||
|
||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||
let showPresence = true;
|
||||
if (enablePresenceByHsUrl && enablePresenceByHsUrl[cli.baseUrl] !== undefined) {
|
||||
showPresence = enablePresenceByHsUrl[cli.baseUrl];
|
||||
}
|
||||
|
||||
let presenceLabel = null;
|
||||
if (showPresence) {
|
||||
const PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
||||
presenceLabel = <PresenceLabel activeAgo={presenceLastActiveAgo}
|
||||
currentlyActive={presenceCurrentlyActive}
|
||||
presenceState={presenceState} />;
|
||||
}
|
||||
|
||||
let statusLabel = null;
|
||||
if (statusMessage) {
|
||||
statusLabel = <span className="mx_UserInfo_statusMessage">{ statusMessage }</span>;
|
||||
}
|
||||
|
||||
const onMemberAvatarClick = useCallback(() => {
|
||||
const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl;
|
||||
if (!avatarUrl) return;
|
||||
|
||||
const httpUrl = cli.mxcUrlToHttp(avatarUrl);
|
||||
const ImageView = sdk.getComponent("elements.ImageView");
|
||||
const params = {
|
||||
src: httpUrl,
|
||||
name: member.name,
|
||||
};
|
||||
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
||||
}, [cli, member]);
|
||||
|
||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
const avatarElement = (
|
||||
<div className="mx_UserInfo_avatar">
|
||||
<div>
|
||||
<div>
|
||||
<MemberAvatar
|
||||
member={member}
|
||||
width={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
height={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
resizeMethod="scale"
|
||||
fallbackUserId={member.userId}
|
||||
onClick={onMemberAvatarClick}
|
||||
urls={member.avatarUrl ? [member.avatarUrl] : undefined} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
let e2eIcon;
|
||||
if (e2eStatus) {
|
||||
e2eIcon = <E2EIcon size={18} status={e2eStatus} isUser={true} />;
|
||||
}
|
||||
|
||||
const displayName = member.name || member.displayname;
|
||||
|
||||
return (
|
||||
<div className={classNames("mx_UserInfo", className)} role="tabpanel">
|
||||
<AutoHideScrollbar className="mx_UserInfo_scrollContainer">
|
||||
{ closeButton }
|
||||
{ avatarElement }
|
||||
|
||||
<div className="mx_UserInfo_container mx_UserInfo_separator">
|
||||
<div className="mx_UserInfo_profile">
|
||||
<div>
|
||||
<h2 aria-label={displayName}>
|
||||
{ e2eIcon }
|
||||
{ displayName }
|
||||
</h2>
|
||||
</div>
|
||||
<div>{ member.userId }</div>
|
||||
<div className="mx_UserInfo_profileStatus">
|
||||
{presenceLabel}
|
||||
{statusLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ children }
|
||||
</AutoHideScrollbar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UserInfo = ({user, groupId, roomId, onClose}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
|
@ -1117,20 +1228,6 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
|
|||
}
|
||||
}, [cli, user.userId]);
|
||||
|
||||
const onMemberAvatarClick = useCallback(() => {
|
||||
const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl;
|
||||
if (!avatarUrl) return;
|
||||
|
||||
const httpUrl = cli.mxcUrlToHttp(avatarUrl);
|
||||
const ImageView = sdk.getComponent("elements.ImageView");
|
||||
const params = {
|
||||
src: httpUrl,
|
||||
name: member.name,
|
||||
};
|
||||
|
||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
||||
}, [cli, member]);
|
||||
|
||||
let synapseDeactivateButton;
|
||||
let spinner;
|
||||
|
||||
|
@ -1180,68 +1277,6 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
|
|||
spinner = <Loader imgClassName="mx_ContextualMenu_spinner" />;
|
||||
}
|
||||
|
||||
const displayName = member.name || member.displayname;
|
||||
|
||||
let presenceState;
|
||||
let presenceLastActiveAgo;
|
||||
let presenceCurrentlyActive;
|
||||
let statusMessage;
|
||||
|
||||
if (member instanceof RoomMember && member.user) {
|
||||
presenceState = member.user.presence;
|
||||
presenceLastActiveAgo = member.user.lastActiveAgo;
|
||||
presenceCurrentlyActive = member.user.currentlyActive;
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
statusMessage = member.user._unstable_statusMessage;
|
||||
}
|
||||
}
|
||||
|
||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||
let showPresence = true;
|
||||
if (enablePresenceByHsUrl && enablePresenceByHsUrl[cli.baseUrl] !== undefined) {
|
||||
showPresence = enablePresenceByHsUrl[cli.baseUrl];
|
||||
}
|
||||
|
||||
let presenceLabel = null;
|
||||
if (showPresence) {
|
||||
const PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
||||
presenceLabel = <PresenceLabel activeAgo={presenceLastActiveAgo}
|
||||
currentlyActive={presenceCurrentlyActive}
|
||||
presenceState={presenceState} />;
|
||||
}
|
||||
|
||||
let statusLabel = null;
|
||||
if (statusMessage) {
|
||||
statusLabel = <span className="mx_UserInfo_statusMessage">{ statusMessage }</span>;
|
||||
}
|
||||
|
||||
// const avatarUrl = user.getMxcAvatarUrl ? user.getMxcAvatarUrl() : user.avatarUrl;
|
||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
const avatarElement = (
|
||||
<div className="mx_UserInfo_avatar">
|
||||
<div>
|
||||
<div>
|
||||
<MemberAvatar
|
||||
member={member}
|
||||
width={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
height={2 * 0.3 * window.innerHeight} // 2x@30vh
|
||||
resizeMethod="scale"
|
||||
fallbackUserId={member.userId}
|
||||
onClick={onMemberAvatarClick}
|
||||
urls={member.avatarUrl ? [member.avatarUrl] : undefined} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
let closeButton;
|
||||
if (onClose) {
|
||||
closeButton = <AccessibleButton className="mx_UserInfo_cancel" onClick={onClose} title={_t('Close')}>
|
||||
<div />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const memberDetails = (
|
||||
<PowerLevelSection
|
||||
powerLevels={powerLevels}
|
||||
|
@ -1347,53 +1382,30 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
|
|||
</div>
|
||||
);
|
||||
|
||||
let e2eIcon;
|
||||
let e2eStatus;
|
||||
if (isRoomEncrypted && devices) {
|
||||
const e2eStatus = _getE2EStatus(cli, user.userId, devices);
|
||||
e2eIcon = <E2EIcon size={18} status={e2eStatus} isUser={true} />;
|
||||
e2eStatus = getE2EStatus(cli, user.userId, devices);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_UserInfo" role="tabpanel">
|
||||
<AutoHideScrollbar className="mx_UserInfo_scrollContainer">
|
||||
{ closeButton }
|
||||
{ avatarElement }
|
||||
return <UserInfoPane onClose={onClose} e2eStatus={e2eStatus} member={member}>
|
||||
{ memberDetails &&
|
||||
<div className="mx_UserInfo_container mx_UserInfo_separator mx_UserInfo_memberDetailsContainer">
|
||||
<div className="mx_UserInfo_memberDetails">
|
||||
{ memberDetails }
|
||||
</div>
|
||||
</div> }
|
||||
|
||||
<div className="mx_UserInfo_container">
|
||||
<div className="mx_UserInfo_profile">
|
||||
<div>
|
||||
<h2 aria-label={displayName}>
|
||||
{ e2eIcon }
|
||||
{ displayName }
|
||||
</h2>
|
||||
</div>
|
||||
<div>{ user.userId }</div>
|
||||
<div className="mx_UserInfo_profileStatus">
|
||||
{presenceLabel}
|
||||
{statusLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{ securitySection }
|
||||
<UserOptionsSection
|
||||
devices={devices}
|
||||
canInvite={roomPermissions.canInvite}
|
||||
isIgnored={isIgnored}
|
||||
member={member} />
|
||||
|
||||
{ memberDetails && <div className="mx_UserInfo_container mx_UserInfo_memberDetailsContainer">
|
||||
<div className="mx_UserInfo_memberDetails">
|
||||
{ memberDetails }
|
||||
</div>
|
||||
</div> }
|
||||
{ adminToolsContainer }
|
||||
|
||||
{ securitySection }
|
||||
<UserOptionsSection
|
||||
devices={devices}
|
||||
canInvite={roomPermissions.canInvite}
|
||||
isIgnored={isIgnored}
|
||||
member={member} />
|
||||
|
||||
{ adminToolsContainer }
|
||||
|
||||
{ spinner }
|
||||
</AutoHideScrollbar>
|
||||
</div>
|
||||
);
|
||||
{ spinner }
|
||||
</UserInfoPane>;
|
||||
};
|
||||
|
||||
UserInfo.propTypes = {
|
||||
|
|
|
@ -1128,6 +1128,8 @@
|
|||
"For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.",
|
||||
"For maximum security, do this in person.": "For maximum security, do this in person.",
|
||||
"Start Verification": "Start Verification",
|
||||
"Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.",
|
||||
"Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Your messages are secured and only you and the recipient have the unique keys to unlock them.",
|
||||
"Members": "Members",
|
||||
"Files": "Files",
|
||||
"Trusted": "Trusted",
|
||||
|
@ -1144,7 +1146,6 @@
|
|||
"<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.",
|
||||
"Security": "Security",
|
||||
"Sunday": "Sunday",
|
||||
"Monday": "Monday",
|
||||
|
|
Loading…
Reference in a new issue