diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss
index 02e5a948e9..542d9d293b 100644
--- a/res/css/structures/_GroupView.scss
+++ b/res/css/structures/_GroupView.scss
@@ -15,10 +15,6 @@ limitations under the License.
*/
.mx_GroupView {
- max-width: 960px;
- width: 100%;
- margin-left: auto;
- margin-right: auto;
display: flex;
flex-direction: column;
overflow: hidden;
@@ -29,7 +25,6 @@ limitations under the License.
}
.mx_GroupView_header {
- max-width: 960px;
min-height: 70px;
align-items: center;
display: flex;
@@ -162,6 +157,11 @@ limitations under the License.
line-height: 2em;
}
+.mx_GroupView > .mx_MainSplit {
+ flex: 1;
+ display: flex;
+}
+
.mx_GroupView_body {
flex-grow: 1;
}
diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss
index a6027f246f..1ccbd19391 100644
--- a/res/css/structures/_MatrixChat.scss
+++ b/res/css/structures/_MatrixChat.scss
@@ -69,7 +69,8 @@ limitations under the License.
transform: translateX(-50%);
}
-.mx_MatrixChat .mx_MatrixChat_middlePanel {
+/* not the left panel, and not the resize handle, so the roomview/groupview/... */
+.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_ResizeHandle) {
background-color: $primary-bg-color;
flex: 1;
@@ -81,8 +82,6 @@ limitations under the License.
*/
overflow-x: auto;
- display: flex;
-
/* To fix https://github.com/vector-im/riot-web/issues/3298 where Safari
needed height 100% all the way down to the HomePage. Height does not
have to be auto, empirically.
diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss
index 1283f05150..c115f074b4 100644
--- a/res/css/structures/_RoomView.scss
+++ b/res/css/structures/_RoomView.scss
@@ -16,20 +16,10 @@ limitations under the License.
.mx_RoomView {
word-wrap: break-word;
- position: relative;
-
display: flex;
- width: 100%;
-
flex-direction: column;
}
-.mx_RoomView .mx_RoomHeader {
- order: 1;
-
- flex: 0 0 52px;
-}
-
.mx_RoomView_fileDropTarget {
min-width: 0px;
width: 100%;
@@ -62,15 +52,11 @@ limitations under the License.
}
.mx_RoomView_auxPanel {
- order: 2;
-
min-width: 0px;
width: 100%;
margin: 0px auto;
overflow: auto;
- border-bottom: 1px solid $primary-hairline-color;
-
flex: 0 0 auto;
}
@@ -79,13 +65,17 @@ limitations under the License.
}
-.mx_RoomView_body {
- order: 3;
+.mx_RoomView .mx_MainSplit {
flex: 1 1 0;
- flex-direction: column;
display: flex;
}
+.mx_RoomView_body {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
.mx_RoomView_body .mx_RoomView_topUnreadMessagesBar {
order: 1;
}
diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss
index 6ff0c4fa17..340954e5f6 100644
--- a/res/css/views/rooms/_MemberList.scss
+++ b/res/css/views/rooms/_MemberList.scss
@@ -24,7 +24,7 @@ limitations under the License.
}
.mx_MemberList .mx_Spinner {
- flex: 0 0 auto;
+ flex: 1 0 auto;
}
.mx_MemberList_chevron {
diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss
index b4a3d27d4f..035df9a9cd 100644
--- a/res/css/views/rooms/_RoomHeader.scss
+++ b/res/css/views/rooms/_RoomHeader.scss
@@ -14,6 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+.mx_RoomHeader {
+ flex: 0 0 52px;
+ 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/PageTypes.js b/src/PageTypes.js
index 66d930c288..60111723fb 100644
--- a/src/PageTypes.js
+++ b/src/PageTypes.js
@@ -20,7 +20,6 @@ export default {
HomePage: "home_page",
RoomView: "room_view",
UserSettings: "user_settings",
- CreateRoom: "create_room",
RoomDirectory: "room_directory",
UserView: "user_view",
GroupView: "group_view",
diff --git a/src/components/structures/CreateRoom.js b/src/components/structures/CreateRoom.js
deleted file mode 100644
index a8aac71479..0000000000
--- a/src/components/structures/CreateRoom.js
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket 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.
-*/
-
-'use strict';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { _t } from '../../languageHandler';
-import sdk from '../../index';
-import MatrixClientPeg from '../../MatrixClientPeg';
-const PresetValues = {
- PrivateChat: "private_chat",
- PublicChat: "public_chat",
- Custom: "custom",
-};
-
-module.exports = React.createClass({
- displayName: 'CreateRoom',
-
- propTypes: {
- onRoomCreated: PropTypes.func,
- collapsedRhs: PropTypes.bool,
- },
-
- phases: {
- CONFIG: "CONFIG", // We're waiting for user to configure and hit create.
- CREATING: "CREATING", // We're sending the request.
- CREATED: "CREATED", // We successfully created the room.
- ERROR: "ERROR", // There was an error while trying to create room.
- },
-
- getDefaultProps: function() {
- return {
- onRoomCreated: function() {},
- };
- },
-
- getInitialState: function() {
- return {
- phase: this.phases.CONFIG,
- error_string: "",
- is_private: true,
- share_history: false,
- default_preset: PresetValues.PrivateChat,
- topic: '',
- room_name: '',
- invited_users: [],
- };
- },
-
- onCreateRoom: function() {
- const options = {};
-
- if (this.state.room_name) {
- options.name = this.state.room_name;
- }
-
- if (this.state.topic) {
- options.topic = this.state.topic;
- }
-
- if (this.state.preset) {
- if (this.state.preset != PresetValues.Custom) {
- options.preset = this.state.preset;
- } else {
- options.initial_state = [
- {
- type: "m.room.join_rules",
- content: {
- "join_rule": this.state.is_private ? "invite" : "public",
- },
- },
- {
- type: "m.room.history_visibility",
- content: {
- "history_visibility": this.state.share_history ? "shared" : "invited",
- },
- },
- ];
- }
- }
-
- options.invite = this.state.invited_users;
-
- const alias = this.getAliasLocalpart();
- if (alias) {
- options.room_alias_name = alias;
- }
-
- const cli = MatrixClientPeg.get();
- if (!cli) {
- // TODO: Error.
- console.error("Cannot create room: No matrix client.");
- return;
- }
-
- const deferred = cli.createRoom(options);
-
- if (this.state.encrypt) {
- // TODO
- }
-
- this.setState({
- phase: this.phases.CREATING,
- });
-
- const self = this;
-
- deferred.then(function(resp) {
- self.setState({
- phase: self.phases.CREATED,
- });
- self.props.onRoomCreated(resp.room_id);
- }, function(err) {
- self.setState({
- phase: self.phases.ERROR,
- error_string: err.toString(),
- });
- });
- },
-
- getPreset: function() {
- return this.refs.presets.getPreset();
- },
-
- getName: function() {
- return this.refs.name_textbox.getName();
- },
-
- getTopic: function() {
- return this.refs.topic.getTopic();
- },
-
- getAliasLocalpart: function() {
- return this.refs.alias.getAliasLocalpart();
- },
-
- getInvitedUsers: function() {
- return this.refs.user_selector.getUserIds();
- },
-
- onPresetChanged: function(preset) {
- switch (preset) {
- case PresetValues.PrivateChat:
- this.setState({
- preset: preset,
- is_private: true,
- share_history: false,
- });
- break;
- case PresetValues.PublicChat:
- this.setState({
- preset: preset,
- is_private: false,
- share_history: true,
- });
- break;
- case PresetValues.Custom:
- this.setState({
- preset: preset,
- });
- break;
- }
- },
-
- onPrivateChanged: function(ev) {
- this.setState({
- preset: PresetValues.Custom,
- is_private: ev.target.checked,
- });
- },
-
- onShareHistoryChanged: function(ev) {
- this.setState({
- preset: PresetValues.Custom,
- share_history: ev.target.checked,
- });
- },
-
- onTopicChange: function(ev) {
- this.setState({
- topic: ev.target.value,
- });
- },
-
- onNameChange: function(ev) {
- this.setState({
- room_name: ev.target.value,
- });
- },
-
- onInviteChanged: function(invited_users) {
- this.setState({
- invited_users: invited_users,
- });
- },
-
- onAliasChanged: function(alias) {
- this.setState({
- alias: alias,
- });
- },
-
- onEncryptChanged: function(ev) {
- this.setState({
- encrypt: ev.target.checked,
- });
- },
-
- render: function() {
- const curr_phase = this.state.phase;
- if (curr_phase == this.phases.CREATING) {
- const Loader = sdk.getComponent("elements.Spinner");
- return (
-
- );
- } else {
- let error_box = "";
- if (curr_phase == this.phases.ERROR) {
- error_box = (
-
- { _t('An error occurred: %(error_string)s', {error_string: this.state.error_string}) }
-
- );
- }
-
- const CreateRoomButton = sdk.getComponent("create_room.CreateRoomButton");
- const RoomAlias = sdk.getComponent("create_room.RoomAlias");
- const Presets = sdk.getComponent("create_room.Presets");
- const UserSelector = sdk.getComponent("elements.UserSelector");
- const SimpleRoomHeader = sdk.getComponent("rooms.SimpleRoomHeader");
-
- const domain = MatrixClientPeg.get().getDomain();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- { _t('Make this room private') }
-
-
-
-
-
- { _t('Share message history with new users') }
-
-
-
-
-
- { _t('Encrypt room') }
-
-
-
-
-
- { error_box }
-
-
- );
- }
- },
-});
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 2c287c1b60..f24524cc5f 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -24,6 +24,9 @@ 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 MainSplit from './MainSplit';
+import RightPanel from './RightPanel';
import Modal from '../../Modal';
import classnames from 'classnames';
@@ -1280,6 +1283,8 @@ export default React.createClass({
}
}
+ const rightPanel = !this.props.collapsedRhs ? : undefined;
+
const headerClasses = {
mx_GroupView_header: true,
mx_GroupView_header_view: !this.state.editing,
@@ -1287,7 +1292,7 @@ export default React.createClass({
};
return (
-
+
@@ -1305,12 +1310,15 @@ export default React.createClass({
{ rightButtons }
+
-
- { this._getMembershipSection() }
- { this._getGroupSection() }
-
-
+
+
+ { this._getMembershipSection() }
+ { this._getGroupSection() }
+
+
+
);
} else if (this.state.error) {
if (this.state.error.httpStatus === 404) {
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 63d5dd2102..585fd0f7d4 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -173,11 +173,7 @@ const LoggedInView = React.createClass({
}
},
onResized: (size, item) => {
- if (item.classList.contains("mx_LeftPanel_container")) {
- window.localStorage.setItem("mx_lhs_size", '' + size);
- } else if(item.classList.contains("mx_RightPanel")) {
- window.localStorage.setItem("mx_rhs_size", '' + size);
- }
+ window.localStorage.setItem("mx_lhs_size", '' + size);
},
};
const resizer = new Resizer(
@@ -193,10 +189,6 @@ const LoggedInView = React.createClass({
if (lhsSize !== null) {
this.resizer.forHandleAt(0).resize(parseInt(lhsSize, 10));
}
- const rhsSize = window.localStorage.getItem("mx_rhs_size");
- if (rhsSize !== null) {
- this.resizer.forHandleAt(1).resize(parseInt(rhsSize, 10));
- }
},
onAccountData: function(event) {
@@ -421,10 +413,8 @@ const LoggedInView = React.createClass({
render: function() {
const LeftPanel = sdk.getComponent('structures.LeftPanel');
- const RightPanel = sdk.getComponent('structures.RightPanel');
const RoomView = sdk.getComponent('structures.RoomView');
const UserSettings = sdk.getComponent('structures.UserSettings');
- const CreateRoom = sdk.getComponent('structures.CreateRoom');
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
const HomePage = sdk.getComponent('structures.HomePage');
const GroupView = sdk.getComponent('structures.GroupView');
@@ -437,7 +427,6 @@ const LoggedInView = React.createClass({
const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar');
let page_element;
- let right_panel = '';
switch (this.props.page_type) {
case PageTypes.RoomView:
@@ -454,9 +443,6 @@ const LoggedInView = React.createClass({
collapsedRhs={this.props.collapseRhs}
ConferenceHandler={this.props.ConferenceHandler}
/>;
- if (!this.props.collapseRhs) {
- right_panel =
;
- }
break;
case PageTypes.UserSettings:
@@ -466,21 +452,12 @@ const LoggedInView = React.createClass({
referralBaseUrl={this.props.config.referralBaseUrl}
teamToken={this.props.teamToken}
/>;
- if (!this.props.collapseRhs) right_panel =
;
break;
case PageTypes.MyGroups:
page_element =
;
break;
- case PageTypes.CreateRoom:
- page_element =
;
- if (!this.props.collapseRhs) right_panel =
;
- break;
-
case PageTypes.RoomDirectory:
page_element =
;
+ // TODO: fix/remove UserView
+ // right_panel =
;
break;
case PageTypes.GroupView:
page_element =
;
- if (!this.props.collapseRhs) right_panel =
;
break;
}
@@ -572,11 +549,7 @@ const LoggedInView = React.createClass({
disabled={this.props.leftDisabled}
/>
-
- { page_element }
-
-
- { right_panel }
+ { page_element }
diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js
new file mode 100644
index 0000000000..a55feb65e4
--- /dev/null
+++ b/src/components/structures/MainSplit.js
@@ -0,0 +1,92 @@
+/*
+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 ResizeHandle from '../views/elements/ResizeHandle';
+import {Resizer, FixedDistributor} from '../../resizer';
+
+export default class MainSplit extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this._setResizeContainerRef = this._setResizeContainerRef.bind(this);
+ }
+
+ _createResizer() {
+ const classNames = {
+ handle: "mx_ResizeHandle",
+ vertical: "mx_ResizeHandle_vertical",
+ reverse: "mx_ResizeHandle_reverse",
+ };
+ const resizer = new Resizer(
+ this.resizeContainer,
+ FixedDistributor);
+ resizer.setClassNames(classNames);
+ const rhsSize = window.localStorage.getItem("mx_rhs_size");
+ if (rhsSize !== null) {
+ resizer.forHandleAt(0).resize(parseInt(rhsSize, 10));
+ }
+
+ resizer.attach();
+ this.resizer = resizer;
+ }
+
+ _setResizeContainerRef(div) {
+ this.resizeContainer = div;
+ }
+
+ componentDidMount() {
+ if (this.props.panel && !this.collapsedRhs) {
+ this._createResizer();
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.resizer) {
+ this.resizer.detach();
+ this.resizer = null;
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const wasExpanded = !this.props.collapsedRhs && prevProps.collapsedRhs;
+ const wasCollapsed = this.props.collapsedRhs && !prevProps.collapsedRhs;
+ const wasPanelSet = this.props.panel && !prevProps.panel;
+ const wasPanelCleared = !this.props.panel && prevProps.panel;
+
+ if (wasExpanded || wasPanelSet) {
+ this._createResizer();
+ } else if (wasCollapsed || wasPanelCleared) {
+ this.resizer.detach();
+ this.resizer = null;
+ }
+ }
+
+ render() {
+ const bodyView = React.Children.only(this.props.children);
+ const panelView = this.props.panel;
+
+ if (this.props.collapsedRhs || !panelView) {
+ return bodyView;
+ } else {
+ return
+ { bodyView }
+
+ { panelView }
+
;
+ }
+ }
+}
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 226b4a4ba4..95261597fe 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,98 +159,41 @@ 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 ? (
+ // inviteGroup =
+ isPhaseGroup ? (
@@ -372,13 +217,8 @@ module.exports = React.createClass({
return (
-
-
- { headerButtons }
-
-
{ panel }
);
- },
-});
+ }
+}
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index dd57bd7636..2d9443efb8 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -43,6 +43,8 @@ const Rooms = require('../../Rooms');
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
+import MainSplit from './MainSplit';
+import RightPanel from './RightPanel';
import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
@@ -1510,18 +1512,20 @@ module.exports = React.createClass({
oobData={this.props.oobData}
collapsedRhs={this.props.collapsedRhs}
/>
-
@@ -1555,16 +1559,18 @@ module.exports = React.createClass({
room={this.state.room}
collapsedRhs={this.props.collapsedRhs}
/>
-
@@ -1811,8 +1817,10 @@ module.exports = React.createClass({
},
);
+ const rightPanel = this.state.room ?
: undefined;
+
return (
-
+
- { auxPanel }
-
- { topUnreadMessagesBar }
- { messagePanel }
- { searchResultsPanel }
-
-
-
- { statusBar }
+
+
+ { auxPanel }
+ { topUnreadMessagesBar }
+ { messagePanel }
+ { searchResultsPanel }
+
+ { messageComposer }
- { messageComposer }
-
-
+
+
);
},
});
diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js
new file mode 100644
index 0000000000..3cb0727928
--- /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..96cd2c6415
--- /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 }
+
);