Decorate Right Panel cards with Space header for when viewing it in that context

This commit is contained in:
Michael Telatynski 2021-03-02 09:51:11 +00:00
parent 1a7a0e619d
commit 8efe7dcaa1
7 changed files with 98 additions and 25 deletions

View file

@ -160,3 +160,20 @@ limitations under the License.
mask-position: center; mask-position: center;
} }
} }
.mx_RightPanel_scopeHeader {
margin: 24px;
text-align: center;
font-weight: $font-semi-bold;
font-size: $font-18px;
line-height: $font-22px;
.mx_BaseAvatar {
margin-right: 8px;
vertical-align: middle;
}
.mx_BaseAvatar_image {
border-radius: 8px;
}
}

View file

@ -19,6 +19,7 @@ limitations under the License.
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
margin-top: 8px;
} }
.mx_MemberInfo_name { .mx_MemberInfo_name {

View file

@ -44,6 +44,10 @@ limitations under the License.
.mx_AutoHideScrollbar { .mx_AutoHideScrollbar {
flex: 1 1 0; flex: 1 1 0;
} }
.mx_RightPanel_scopeHeader {
margin-top: -8px;
}
} }
.mx_GroupMemberList_query, .mx_GroupMemberList_query,

View file

@ -60,7 +60,9 @@ import QuestionDialog from "../dialogs/QuestionDialog";
import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog"; import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog";
import InfoDialog from "../dialogs/InfoDialog"; import InfoDialog from "../dialogs/InfoDialog";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
interface IDevice { interface IDevice {
deviceId: string; deviceId: string;
@ -302,7 +304,8 @@ const UserOptionsSection: React.FC<{
member: RoomMember; member: RoomMember;
isIgnored: boolean; isIgnored: boolean;
canInvite: boolean; canInvite: boolean;
}> = ({member, isIgnored, canInvite}) => { isSpace?: boolean;
}> = ({member, isIgnored, canInvite, isSpace}) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
let ignoreButton = null; let ignoreButton = null;
@ -342,7 +345,7 @@ const UserOptionsSection: React.FC<{
</AccessibleButton> </AccessibleButton>
); );
if (member.roomId) { if (member.roomId && !isSpace) {
const onReadReceiptButton = function() { const onReadReceiptButton = function() {
const room = cli.getRoom(member.roomId); const room = cli.getRoom(member.roomId);
dis.dispatch({ dis.dispatch({
@ -434,14 +437,18 @@ const UserOptionsSection: React.FC<{
); );
}; };
const warnSelfDemote = async () => { const warnSelfDemote = async (isSpace) => {
const {finished} = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, { const {finished} = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, {
title: _t("Demote yourself?"), title: _t("Demote yourself?"),
description: description:
<div> <div>
{ _t("You will not be able to undo this change as you are demoting yourself, " + { isSpace
"if you are the last privileged user in the room it will be impossible " + ? _t("You will not be able to undo this change as you are demoting yourself, " +
"to regain privileges.") } "if you are the last privileged user in the space it will be impossible " +
"to regain privileges.")
: _t("You will not be able to undo this change as you are demoting yourself, " +
"if you are the last privileged user in the room it will be impossible " +
"to regain privileges.") }
</div>, </div>,
button: _t("Demote"), button: _t("Demote"),
}); });
@ -717,7 +724,7 @@ const MuteToggleButton: React.FC<IBaseRoomProps> = ({member, room, powerLevels,
// 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(room?.isSpaceRoom()))) 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;
@ -806,7 +813,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
if (canAffectUser && me.powerLevel >= kickPowerLevel) { if (canAffectUser && me.powerLevel >= kickPowerLevel) {
kickButton = <RoomKickButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />; kickButton = <RoomKickButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
} }
if (me.powerLevel >= redactPowerLevel) { if (me.powerLevel >= redactPowerLevel && !room.isSpaceRoom()) {
redactButton = ( redactButton = (
<RedactMessagesButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} /> <RedactMessagesButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />
); );
@ -1085,7 +1092,7 @@ const PowerLevelEditor: React.FC<{
} else if (myUserId === target) { } else if (myUserId === target) {
// 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.
try { try {
if (!(await warnSelfDemote())) return; if (!(await warnSelfDemote(room?.isSpaceRoom()))) return;
} catch (e) { } catch (e) {
console.error("Failed to warn about self demotion: ", e); console.error("Failed to warn about self demotion: ", e);
} }
@ -1315,12 +1322,10 @@ const BasicUserInfo: React.FC<{
if (!isRoomEncrypted) { if (!isRoomEncrypted) {
if (!cryptoEnabled) { if (!cryptoEnabled) {
text = _t("This client does not support end-to-end encryption."); text = _t("This client does not support end-to-end encryption.");
} else if (room) { } else if (room && !room.isSpaceRoom()) {
text = _t("Messages in this room are not end-to-end encrypted."); text = _t("Messages in this room are not end-to-end encrypted.");
} else {
// TODO what to render for GroupMember
} }
} else { } else if (!room.isSpaceRoom()) {
text = _t("Messages in this room are end-to-end encrypted."); text = _t("Messages in this room are end-to-end encrypted.");
} }
@ -1381,7 +1386,9 @@ const BasicUserInfo: React.FC<{
<UserOptionsSection <UserOptionsSection
canInvite={roomPermissions.canInvite} canInvite={roomPermissions.canInvite}
isIgnored={isIgnored} isIgnored={isIgnored}
member={member} /> member={member}
isSpace={room?.isSpaceRoom()}
/>
{ adminToolsContainer } { adminToolsContainer }
@ -1498,7 +1505,7 @@ interface IProps {
user: Member; user: Member;
groupId?: string; groupId?: string;
room?: Room; room?: Room;
phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo; phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo | RightPanelPhases.SpaceMemberInfo;
onClose(): void; onClose(): void;
} }
@ -1542,7 +1549,9 @@ const UserInfo: React.FC<Props> = ({
previousPhase = RightPanelPhases.RoomMemberInfo; previousPhase = RightPanelPhases.RoomMemberInfo;
refireParams = {member: member}; refireParams = {member: member};
} else if (room) { } else if (room) {
previousPhase = RightPanelPhases.RoomMemberList; previousPhase = previousPhase = room.isSpaceRoom()
? RightPanelPhases.SpaceMemberList
: RightPanelPhases.RoomMemberList;
} }
const onEncryptionPanelClose = () => { const onEncryptionPanelClose = () => {
@ -1557,6 +1566,7 @@ const UserInfo: React.FC<Props> = ({
switch (phase) { switch (phase) {
case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.RoomMemberInfo:
case RightPanelPhases.GroupMemberInfo: case RightPanelPhases.GroupMemberInfo:
case RightPanelPhases.SpaceMemberInfo:
content = ( content = (
<BasicUserInfo <BasicUserInfo
room={room} room={room}
@ -1587,7 +1597,18 @@ const UserInfo: React.FC<Props> = ({
} }
} }
const header = <UserInfoHeader member={member} e2eStatus={e2eStatus} />; let scopeHeader;
if (room?.isSpaceRoom()) {
scopeHeader = <div className="mx_RightPanel_scopeHeader">
<RoomAvatar room={room} height={32} width={32} />
<RoomName room={room} />
</div>;
}
const header = <React.Fragment>
{ scopeHeader }
<UserInfoHeader member={member} e2eStatus={e2eStatus} />
</React.Fragment>;
return <BaseCard return <BaseCard
className={classes.join(" ")} className={classes.join(" ")}
header={header} header={header}

View file

@ -27,6 +27,8 @@ import * as sdk from "../../../index";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
import BaseCard from "../right_panel/BaseCard"; import BaseCard from "../right_panel/BaseCard";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5; const INITIAL_LOAD_NUM_INVITED = 5;
@ -456,6 +458,8 @@ export default class MemberList extends React.Component {
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat(); const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
if (chat && chat.roomId === this.props.roomId) { if (chat && chat.roomId === this.props.roomId) {
inviteButtonText = _t("Invite to this community"); inviteButtonText = _t("Invite to this community");
} else if (room.isSpaceRoom()) {
inviteButtonText = _t("Invite to this space");
} }
const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
@ -483,12 +487,26 @@ export default class MemberList extends React.Component {
onSearch={ this.onSearchQueryChanged } /> onSearch={ this.onSearchQueryChanged } />
); );
let previousPhase = RightPanelPhases.RoomSummary;
// We have no previousPhase for when viewing a MemberList from a Space
let scopeHeader;
if (room?.isSpaceRoom()) {
previousPhase = undefined;
scopeHeader = <div className="mx_RightPanel_scopeHeader">
<RoomAvatar room={room} height={32} width={32} />
<RoomName room={room} />
</div>;
}
return <BaseCard return <BaseCard
className="mx_MemberList" className="mx_MemberList"
header={inviteButton} header={<React.Fragment>
{ scopeHeader }
{ inviteButton }
</React.Fragment>}
footer={footer} footer={footer}
onClose={this.props.onClose} onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary} previousPhase={previousPhase}
> >
<div className="mx_MemberList_wrapper"> <div className="mx_MemberList_wrapper">
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined} <TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}

View file

@ -23,6 +23,8 @@ import dis from "../../../dispatcher/dispatcher";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import {isValid3pidInvite} from "../../../RoomInvite"; import {isValid3pidInvite} from "../../../RoomInvite";
import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
export default class ThirdPartyMemberInfo extends React.Component { export default class ThirdPartyMemberInfo extends React.Component {
static propTypes = { static propTypes = {
@ -32,14 +34,14 @@ export default class ThirdPartyMemberInfo extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId()); this.room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId());
const me = room.getMember(MatrixClientPeg.get().getUserId()); const me = this.room.getMember(MatrixClientPeg.get().getUserId());
const powerLevels = room.currentState.getStateEvents("m.room.power_levels", ""); const powerLevels = this.room.currentState.getStateEvents("m.room.power_levels", "");
let kickLevel = powerLevels ? powerLevels.getContent().kick : 50; let kickLevel = powerLevels ? powerLevels.getContent().kick : 50;
if (typeof(kickLevel) !== 'number') kickLevel = 50; if (typeof(kickLevel) !== 'number') kickLevel = 50;
const sender = room.getMember(this.props.event.getSender()); const sender = this.room.getMember(this.props.event.getSender());
this.state = { this.state = {
stateKey: this.props.event.getStateKey(), stateKey: this.props.event.getStateKey(),
@ -119,9 +121,18 @@ export default class ThirdPartyMemberInfo extends React.Component {
); );
} }
let scopeHeader;
if (this.room.isSpaceRoom()) {
scopeHeader = <div className="mx_RightPanel_scopeHeader">
<RoomAvatar room={this.room} height={32} width={32} />
<RoomName room={this.room} />
</div>;
}
// We shamelessly rip off the MemberInfo styles here. // We shamelessly rip off the MemberInfo styles here.
return ( return (
<div className="mx_MemberInfo" role="tabpanel"> <div className="mx_MemberInfo" role="tabpanel">
{ scopeHeader }
<div className="mx_MemberInfo_name"> <div className="mx_MemberInfo_name">
<AccessibleButton className="mx_MemberInfo_cancel" <AccessibleButton className="mx_MemberInfo_cancel"
onClick={this.onCancel} onClick={this.onCancel}

View file

@ -1436,6 +1436,7 @@
"and %(count)s others...|one": "and one other...", "and %(count)s others...|one": "and one other...",
"Invite to this room": "Invite to this room", "Invite to this room": "Invite to this room",
"Invite to this community": "Invite to this community", "Invite to this community": "Invite to this community",
"Invite to this space": "Invite to this space",
"Invited": "Invited", "Invited": "Invited",
"Filter room members": "Filter room members", "Filter room members": "Filter room members",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
@ -1698,6 +1699,7 @@
"Share Link to User": "Share Link to User", "Share Link to User": "Share Link to User",
"Direct message": "Direct message", "Direct message": "Direct message",
"Demote yourself?": "Demote yourself?", "Demote yourself?": "Demote yourself?",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.",
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.", "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.",
"Demote": "Demote", "Demote": "Demote",
"Disinvite": "Disinvite", "Disinvite": "Disinvite",
@ -2167,7 +2169,6 @@
"Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).", "Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
"Go": "Go", "Go": "Go",
"Invite to this space": "Invite to this space",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.", "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.", "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.", "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.",