diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss
index f7c3e902bc..8b36517207 100644
--- a/res/css/views/rooms/_RoomHeader.scss
+++ b/res/css/views/rooms/_RoomHeader.scss
@@ -20,7 +20,6 @@ limitations under the License.
border-bottom: 1px solid $primary-hairline-color;
}
-
/* add 20px to the height of the header when editing */
.mx_RoomHeader_editing {
flex: 0 0 93px ! important;
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 5d23194702..d83d8e2cf1 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -24,6 +24,7 @@ import dis from '../../dispatcher';
import { sanitizedHtmlNode } from '../../HtmlUtils';
import { _t, _td } from '../../languageHandler';
import AccessibleButton from '../views/elements/AccessibleButton';
+import GroupHeaderButtons from '../views/right_panel/GroupHeaderButtons';
import Modal from '../../Modal';
import classnames from 'classnames';
@@ -1305,6 +1306,7 @@ export default React.createClass({
{ rightButtons }
+
{ this._getMembershipSection() }
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 226b4a4ba4..574631a8ae 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -2,6 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
+Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,79 +24,27 @@ import { _t } from '../../languageHandler';
import sdk from '../../index';
import dis from '../../dispatcher';
import { MatrixClient } from 'matrix-js-sdk';
-import Analytics from '../../Analytics';
import RateLimitedFunc from '../../ratelimitedfunc';
import AccessibleButton from '../../components/views/elements/AccessibleButton';
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
import GroupStore from '../../stores/GroupStore';
-class HeaderButton extends React.Component {
- constructor() {
- super();
- this.onClick = this.onClick.bind(this);
+export default class RightPanel extends React.Component {
+
+ static get propTypes() {
+ return {
+ roomId: React.PropTypes.string, // if showing panels for a given room, this is set
+ groupId: React.PropTypes.string, // if showing panels for a given group, this is set
+ };
}
- onClick(ev) {
- Analytics.trackEvent(...this.props.analytics);
- dis.dispatch({
- action: 'view_right_panel_phase',
- phase: this.props.clickPhase,
- });
+ static get contextTypes() {
+ return {
+ matrixClient: PropTypes.instanceOf(MatrixClient),
+ };
}
- render() {
- const TintableSvg = sdk.getComponent("elements.TintableSvg");
- const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
-
- const classes = classNames({
- mx_RightPanel_headerButton: true,
- mx_RightPanel_headerButton_highlight: this.props.isHighlighted,
- });
-
- return
-
- ;
- }
-}
-
-HeaderButton.propTypes = {
- // Whether this button is highlighted
- isHighlighted: PropTypes.bool.isRequired,
- // The phase to swap to when the button is clicked
- clickPhase: PropTypes.string.isRequired,
- // The source file of the icon to display
- iconSrc: PropTypes.string.isRequired,
-
- // The badge to display above the icon
- badge: PropTypes.node,
- // The parameters to track the click event
- analytics: PropTypes.arrayOf(PropTypes.string).isRequired,
-
- // Button title
- title: PropTypes.string.isRequired,
-};
-
-module.exports = React.createClass({
- displayName: 'RightPanel',
-
- propTypes: {
- // TODO: We're trying to move away from these being props, but we need to know
- // whether we should be displaying a room or group member list
- roomId: React.PropTypes.string, // if showing panels for a given room, this is set
- groupId: React.PropTypes.string, // if showing panels for a given group, this is set
- collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
- },
-
- contextTypes: {
- matrixClient: PropTypes.instanceOf(MatrixClient),
- },
-
- Phase: {
+ static Phase = Object.freeze({
RoomMemberList: 'RoomMemberList',
GroupMemberList: 'GroupMemberList',
GroupRoomList: 'GroupRoomList',
@@ -104,147 +53,100 @@ module.exports = React.createClass({
NotificationPanel: 'NotificationPanel',
RoomMemberInfo: 'RoomMemberInfo',
GroupMemberInfo: 'GroupMemberInfo',
- },
+ });
- componentWillMount: function() {
+ constructor(props, context) {
+ super(props, context);
+ this.state = {
+ phase: this.props.groupId ? RightPanel.Phase.GroupMemberList : RightPanel.Phase.RoomMemberList,
+ isUserPrivilegedInGroup: null,
+ };
+ this.onAction = this.onAction.bind(this);
+ this.onRoomStateMember = this.onRoomStateMember.bind(this);
+ this.onGroupStoreUpdated = this.onGroupStoreUpdated.bind(this);
+ this.onInviteToGroupButtonClick = this.onInviteToGroupButtonClick.bind(this);
+ this.onAddRoomToGroupButtonClick = this.onAddRoomToGroupButtonClick.bind(this);
+
+ this._delayedUpdate = new RateLimitedFunc(() => {
+ this.forceUpdate();
+ }, 500);
+ }
+
+ componentWillMount() {
this.dispatcherRef = dis.register(this.onAction);
const cli = this.context.matrixClient;
cli.on("RoomState.members", this.onRoomStateMember);
this._initGroupStore(this.props.groupId);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
dis.unregister(this.dispatcherRef);
if (this.context.matrixClient) {
this.context.matrixClient.removeListener("RoomState.members", this.onRoomStateMember);
}
this._unregisterGroupStore(this.props.groupId);
- },
-
- getInitialState: function() {
- return {
- phase: this.props.groupId ? this.Phase.GroupMemberList : this.Phase.RoomMemberList,
- isUserPrivilegedInGroup: null,
- };
- },
+ }
componentWillReceiveProps(newProps) {
if (newProps.groupId !== this.props.groupId) {
this._unregisterGroupStore(this.props.groupId);
this._initGroupStore(newProps.groupId);
}
- },
+ }
_initGroupStore(groupId) {
if (!groupId) return;
GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
- },
+ }
_unregisterGroupStore() {
GroupStore.unregisterListener(this.onGroupStoreUpdated);
- },
+ }
- onGroupStoreUpdated: function() {
+ onGroupStoreUpdated() {
this.setState({
isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId),
});
- },
+ }
- onCollapseClick: function() {
- dis.dispatch({
- action: 'hide_right_panel',
- });
- },
-
- onInviteToGroupButtonClick: function() {
+ onInviteToGroupButtonClick() {
showGroupInviteDialog(this.props.groupId).then(() => {
this.setState({
- phase: this.Phase.GroupMemberList,
+ phase: RightPanel.Phase.GroupMemberList,
});
});
- },
+ }
- onAddRoomToGroupButtonClick: function() {
+ onAddRoomToGroupButtonClick() {
showGroupAddRoomDialog(this.props.groupId).then(() => {
this.forceUpdate();
});
- },
+ }
- onRoomStateMember: function(ev, state, member) {
+ onRoomStateMember(ev, state, member) {
if (member.roomId !== this.props.roomId) {
return;
}
// redraw the badge on the membership list
- if (this.state.phase === this.Phase.RoomMemberList && member.roomId === this.props.roomId) {
+ if (this.state.phase === RightPanel.Phase.RoomMemberList && member.roomId === this.props.roomId) {
this._delayedUpdate();
- } else if (this.state.phase === this.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
+ } else if (this.state.phase === RightPanel.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level)
this._delayedUpdate();
}
- },
+ }
- _delayedUpdate: new RateLimitedFunc(function() {
- this.forceUpdate(); // eslint-disable-line babel/no-invalid-this
- }, 500),
-
- onAction: function(payload) {
- if (payload.action === "view_user") {
- dis.dispatch({
- action: 'show_right_panel',
- });
- if (payload.member) {
- this.setState({
- phase: this.Phase.RoomMemberInfo,
- member: payload.member,
- });
- } else {
- if (this.props.roomId) {
- this.setState({
- phase: this.Phase.RoomMemberList,
- });
- } else if (this.props.groupId) {
- this.setState({
- phase: this.Phase.GroupMemberList,
- member: payload.member,
- });
- }
- }
- } else if (payload.action === "view_group") {
- this.setState({
- phase: this.Phase.GroupMemberList,
- member: null,
- });
- } else if (payload.action === "view_group_room") {
- this.setState({
- phase: this.Phase.GroupRoomInfo,
- groupRoomId: payload.groupRoomId,
- });
- } else if (payload.action === "view_group_room_list") {
- this.setState({
- phase: this.Phase.GroupRoomList,
- });
- } else if (payload.action === "view_group_member_list") {
- this.setState({
- phase: this.Phase.GroupMemberList,
- });
- } else if (payload.action === "view_group_user") {
- this.setState({
- phase: this.Phase.GroupMemberInfo,
- member: payload.member,
- });
- } else if (payload.action === "view_room") {
- this.setState({
- phase: this.Phase.RoomMemberList,
- });
- } else if (payload.action === "view_right_panel_phase") {
+ onAction(payload) {
+ if (payload.action === "view_right_panel_phase") {
this.setState({
phase: payload.phase,
+ member: payload.member,
});
}
- },
+ }
- render: function() {
+ render() {
const MemberList = sdk.getComponent('rooms.MemberList');
const MemberInfo = sdk.getComponent('rooms.MemberInfo');
const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
@@ -257,96 +159,40 @@ module.exports = React.createClass({
const TintableSvg = sdk.getComponent("elements.TintableSvg");
- // eslint-disable-next-line no-unused-vars
let inviteGroup;
- let membersBadge;
- const membersTitle = _t('Members');
-
const isPhaseGroup = [
- this.Phase.GroupMemberInfo,
- this.Phase.GroupMemberList,
+ RightPanel.Phase.GroupMemberInfo,
+ RightPanel.Phase.GroupMemberList,
].includes(this.state.phase);
- let headerButtons = [];
- if (this.props.roomId) {
- headerButtons = [
- ,
- ,
- ,
- ];
- } else if (this.props.groupId) {
- headerButtons = [
- ,
- ,
- ];
- }
-
- if (this.props.roomId || this.props.groupId) {
- // Hiding the right panel hides it completely and relies on an 'expand' button
- // being put in the RoomHeader or GroupView header, so only show the minimise
- // button on these 2 screens or you won't be able to re-expand the panel.
- headerButtons.push(
-
-
- ,
- );
- }
-
let panel = ;
- if (!this.props.collapsed) {
- if (this.props.roomId && this.state.phase === this.Phase.RoomMemberList) {
- panel = ;
- } else if (this.props.groupId && this.state.phase === this.Phase.GroupMemberList) {
- panel = ;
- } else if (this.state.phase === this.Phase.GroupRoomList) {
- panel = ;
- } else if (this.state.phase === this.Phase.RoomMemberInfo) {
- panel = ;
- } else if (this.state.phase === this.Phase.GroupMemberInfo) {
- panel = ;
- } else if (this.state.phase === this.Phase.GroupRoomInfo) {
- panel = ;
- } else if (this.state.phase === this.Phase.NotificationPanel) {
- panel = ;
- } else if (this.state.phase === this.Phase.FilePanel) {
- panel = ;
- }
- }
-
- if (!panel) {
- panel = ;
+
+ if (this.props.roomId && this.state.phase === RightPanel.Phase.RoomMemberList) {
+ panel = ;
+ } else if (this.props.groupId && this.state.phase === RightPanel.Phase.GroupMemberList) {
+ panel = ;
+ } else if (this.state.phase === RightPanel.Phase.GroupRoomList) {
+ panel = ;
+ } else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) {
+ panel = ;
+ } else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) {
+ panel = ;
+ } else if (this.state.phase === RightPanel.Phase.GroupRoomInfo) {
+ panel = ;
+ } else if (this.state.phase === RightPanel.Phase.NotificationPanel) {
+ panel = ;
+ } else if (this.state.phase === RightPanel.Phase.FilePanel) {
+ panel = ;
}
+ // TODO: either include this in the DOM again, or move it to other component
if (this.props.groupId && this.state.isUserPrivilegedInGroup) {
inviteGroup = isPhaseGroup ? (
@@ -372,13 +218,8 @@ module.exports = React.createClass({
return (
);
- },
-});
+ }
+}
diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js
new file mode 100644
index 0000000000..6d7d9da3a7
--- /dev/null
+++ b/src/components/views/right_panel/GroupHeaderButtons.js
@@ -0,0 +1,81 @@
+/*
+Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
+Copyright 2017 New Vector Ltd
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import { _t } from '../../../languageHandler';
+import dis from '../../../dispatcher';
+import HeaderButton from './HeaderButton';
+import HeaderButtons from './HeaderButtons';
+import RightPanel from '../../structures/RightPanel';
+
+export default class GroupHeaderButtons extends HeaderButtons {
+
+ constructor(props) {
+ super(props, RightPanel.Phase.GroupMemberList);
+ }
+
+ onAction(payload) {
+ super.onAction(payload);
+
+ if (payload.action === "view_user") {
+ dis.dispatch({
+ action: 'show_right_panel',
+ });
+ if (payload.member) {
+ this.setPhase(RightPanel.Phase.RoomMemberInfo, {member: payload.member});
+ } else {
+ this.setPhase(RightPanel.Phase.GroupMemberList);
+ }
+ } else if (payload.action === "view_group") {
+ this.setPhase(RightPanel.Phase.GroupMemberList);
+ } else if (payload.action === "view_group_room") {
+ this.setPhase(RightPanel.Phase.GroupRoomInfo, {groupRoomId: payload.groupRoomId});
+ } else if (payload.action === "view_group_room_list") {
+ this.setPhase(RightPanel.Phase.GroupRoomList);
+ } else if (payload.action === "view_group_member_list") {
+ this.setPhase(RightPanel.Phase.GroupMemberList);
+ } else if (payload.action === "view_group_user") {
+ this.setPhase(RightPanel.Phase.GroupMemberInfo, {member: payload.member});
+ }
+ }
+
+ renderButtons() {
+ const isPhaseGroup = [
+ RightPanel.Phase.GroupMemberInfo,
+ RightPanel.Phase.GroupMemberList,
+ ].includes(this.state.phase);
+ const isPhaseRoom = [
+ RightPanel.Phase.GroupRoomList,
+ RightPanel.Phase.GroupRoomInfo,
+ ].includes(this.state.phase);
+
+ return [
+ ,
+
+ ];
+ }
+}
diff --git a/src/components/views/right_panel/HeaderButton.js b/src/components/views/right_panel/HeaderButton.js
new file mode 100644
index 0000000000..a01d3444f1
--- /dev/null
+++ b/src/components/views/right_panel/HeaderButton.js
@@ -0,0 +1,74 @@
+/*
+Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
+Copyright 2017 New Vector Ltd
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import dis from '../../../dispatcher';
+import Analytics from '../../../Analytics';
+import AccessibleButton from '../elements/AccessibleButton';
+import TintableSvg from '../elements/TintableSvg';
+
+export default class HeaderButton extends React.Component {
+ constructor() {
+ super();
+ this.onClick = this.onClick.bind(this);
+ }
+
+ onClick(ev) {
+ Analytics.trackEvent(...this.props.analytics);
+ dis.dispatch({
+ action: 'view_right_panel_phase',
+ phase: this.props.clickPhase,
+ });
+ }
+
+ render() {
+ const classes = classNames({
+ mx_RightPanel_headerButton: true,
+ mx_RightPanel_headerButton_highlight: this.props.isHighlighted,
+ });
+
+ return
+
+ ;
+ }
+}
+
+HeaderButton.propTypes = {
+ // Whether this button is highlighted
+ isHighlighted: PropTypes.bool.isRequired,
+ // The phase to swap to when the button is clicked
+ clickPhase: PropTypes.string.isRequired,
+ // The source file of the icon to display
+ iconSrc: PropTypes.string.isRequired,
+
+ // The badge to display above the icon
+ badge: PropTypes.node,
+ // The parameters to track the click event
+ analytics: PropTypes.arrayOf(PropTypes.string).isRequired,
+
+ // Button title
+ title: PropTypes.string.isRequired,
+};
diff --git a/src/components/views/right_panel/HeaderButtons.js b/src/components/views/right_panel/HeaderButtons.js
new file mode 100644
index 0000000000..d47e923806
--- /dev/null
+++ b/src/components/views/right_panel/HeaderButtons.js
@@ -0,0 +1,65 @@
+/*
+Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
+Copyright 2017 New Vector Ltd
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import dis from '../../../dispatcher';
+
+export default class HeaderButtons extends React.Component {
+
+ constructor(props, initialPhase) {
+ super(props);
+
+ this.state = {
+ phase: initialPhase,
+ isUserPrivilegedInGroup: null,
+ };
+ this.onAction = this.onAction.bind(this);
+ }
+
+ componentWillMount() {
+ this.dispatcherRef = dis.register(this.onAction);
+ }
+
+ componentWillUnmount() {
+ dis.unregister(this.dispatcherRef);
+ }
+
+ setPhase(phase, extras) {
+ // TODO: delay?
+ dis.dispatch(Object.assign({
+ action: 'view_right_panel_phase',
+ phase: phase,
+ }, extras));
+ }
+
+ onAction(payload) {
+ if (payload.action === "view_right_panel_phase") {
+ this.setState({
+ phase: payload.phase,
+ });
+ }
+ }
+
+ render() {
+ // inline style as this will be swapped around in future commits
+ return
+ { this.renderButtons() }
+
;
+ }
+}
diff --git a/src/components/views/right_panel/RoomHeaderButtons.js b/src/components/views/right_panel/RoomHeaderButtons.js
new file mode 100644
index 0000000000..1d33afd9a8
--- /dev/null
+++ b/src/components/views/right_panel/RoomHeaderButtons.js
@@ -0,0 +1,73 @@
+/*
+Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
+Copyright 2017 New Vector Ltd
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import { _t } from '../../../languageHandler';
+import dis from '../../../dispatcher';
+import HeaderButton from './HeaderButton';
+import HeaderButtons from './HeaderButtons';
+import RightPanel from '../../structures/RightPanel';
+
+export default class RoomHeaderButtons extends HeaderButtons {
+
+ constructor(props) {
+ super(props, RightPanel.Phase.RoomMemberList);
+ }
+
+ onAction(payload) {
+ super.onAction(payload);
+ if (payload.action === "view_user") {
+ dis.dispatch({
+ action: 'show_right_panel',
+ });
+ if (payload.member) {
+ this.setPhase(RightPanel.Phase.RoomMemberInfo, {member: payload.member});
+ } else {
+ this.setPhase(RightPanel.Phase.RoomMemberList);
+ }
+ } else if (payload.action === "view_room") {
+ this.setPhase(RightPanel.Phase.RoomMemberList);
+ }
+ }
+
+ renderButtons() {
+ const isMembersPhase = [
+ RightPanel.Phase.RoomMemberList,
+ RightPanel.Phase.RoomMemberInfo,
+ ].includes(this.state.phase);
+
+ return [
+ ,
+ ,
+
+ ];
+ }
+}
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js
index 8a98f2a8bc..1cc04b1168 100644
--- a/src/components/views/rooms/RoomHeader.js
+++ b/src/components/views/rooms/RoomHeader.js
@@ -33,6 +33,7 @@ import AccessibleButton from '../elements/AccessibleButton';
import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader';
import SettingsStore from "../../../settings/SettingsStore";
+import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
linkifyMatrix(linkify);
@@ -432,6 +433,7 @@ module.exports = React.createClass({
{ saveButton }
{ cancelButton }
{ rightRow }
+
);