diff --git a/electron_app/src/electron-main.js b/electron_app/src/electron-main.js
index ce5ac384..4ffe2110 100644
--- a/electron_app/src/electron-main.js
+++ b/electron_app/src/electron-main.js
@@ -84,7 +84,7 @@ let powerSaveBlockerId;
electron.ipcMain.on('app_onAction', function(ev, payload) {
switch (payload.action) {
case 'call_state':
- if (powerSaveBlockerId && powerSaveBlockerId.isStarted(powerSaveBlockerId)) {
+ if (powerSaveBlockerId && electron.powerSaveBlocker.isStarted(powerSaveBlockerId)) {
if (payload.state === 'ended') {
electron.powerSaveBlocker.stop(powerSaveBlockerId);
}
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 9120de8c..62adaa74 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -1,5 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
+Copyright 2017 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.
@@ -14,8 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-'use strict';
-
import React from 'react';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import sdk from 'matrix-react-sdk';
@@ -26,26 +26,31 @@ import Analytics from 'matrix-react-sdk/lib/Analytics';
import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
import Modal from 'matrix-react-sdk/lib/Modal';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
+import { showGroupInviteDialog } from 'matrix-react-sdk/lib/GroupInvite';
module.exports = React.createClass({
displayName: 'RightPanel',
propTypes: {
- // TODO: This should not be a prop, it should be received from the RoomViewStore
+ // 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
},
Phase: {
- MemberList: 'MemberList',
+ RoomMemberList: 'RoomMemberList',
+ GroupMemberList: 'GroupMemberList',
FilePanel: 'FilePanel',
NotificationPanel: 'NotificationPanel',
- MemberInfo: 'MemberInfo',
+ RoomMemberInfo: 'RoomMemberInfo',
+ GroupMemberInfo: 'GroupMemberInfo',
},
componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction);
- var cli = MatrixClientPeg.get();
+ const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
},
@@ -57,14 +62,20 @@ module.exports = React.createClass({
},
getInitialState: function() {
- return {
- phase: this.Phase.MemberList
- };
+ if (this.props.groupId) {
+ return {
+ phase: this.Phase.GroupMemberList,
+ };
+ } else {
+ return {
+ phase: this.Phase.RoomMemberList,
+ };
+ }
},
onMemberListButtonClick: function() {
Analytics.trackEvent('Right Panel', 'Member List Button', 'click');
- this.setState({ phase: this.Phase.MemberList });
+ this.setState({ phase: this.Phase.RoomMemberList });
},
onFileListButtonClick: function() {
@@ -89,19 +100,23 @@ module.exports = React.createClass({
return;
}
- // call ChatInviteDialog
- dis.dispatch({
- action: 'view_invite',
- roomId: this.props.roomId,
- });
+ if (this.state.phase === this.Phase.GroupMemberList) {
+ showGroupInviteDialog(this.props.groupId);
+ } else {
+ // call UserPickerDialog
+ dis.dispatch({
+ action: 'view_invite',
+ roomId: this.props.roomId,
+ });
+ }
},
onRoomStateMember: function(ev, state, member) {
// redraw the badge on the membership list
- if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) {
+ if (this.state.phase == this.Phase.RoomMemberList && member.roomId === this.props.roomId) {
this._delayedUpdate();
}
- else if (this.state.phase === this.Phase.MemberInfo && member.roomId === this.props.roomId &&
+ else if (this.state.phase === this.Phase.RoomMemberInfo && member.roomId === this.props.roomId &&
member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level)
this._delayedUpdate();
@@ -119,39 +134,55 @@ module.exports = React.createClass({
});
if (payload.member) {
this.setState({
- phase: this.Phase.MemberInfo,
+ 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,
+ groupId: payload.groupId,
+ member: payload.member,
+ });
+ }
}
- else {
- this.setState({
- phase: this.Phase.MemberList
- });
- }
- }
- else if (payload.action === "view_room") {
- if (this.state.phase === this.Phase.MemberInfo) {
- this.setState({
- phase: this.Phase.MemberList
- });
- }
+ } else if (payload.action === "view_group") {
+ this.setState({
+ phase: this.Phase.GroupMemberList,
+ groupId: payload.groupId,
+ member: null,
+ });
+ } else if (payload.action === "view_group_user") {
+ this.setState({
+ phase: this.Phase.GroupMemberInfo,
+ groupId: payload.groupId,
+ member: payload.member,
+ });
+ } else if (payload.action === "view_room") {
+ this.setState({
+ phase: this.Phase.RoomMemberList
+ });
}
},
render: function() {
- var MemberList = sdk.getComponent('rooms.MemberList');
- var NotificationPanel = sdk.getComponent('structures.NotificationPanel');
- var FilePanel = sdk.getComponent('structures.FilePanel');
- var TintableSvg = sdk.getComponent("elements.TintableSvg");
- var buttonGroup;
- var inviteGroup;
- var panel;
+ const MemberList = sdk.getComponent('rooms.MemberList');
+ const GroupMemberList = sdk.getComponent('groups.GroupMemberList');
+ const NotificationPanel = sdk.getComponent('structures.NotificationPanel');
+ const FilePanel = sdk.getComponent('structures.FilePanel');
+ const TintableSvg = sdk.getComponent("elements.TintableSvg");
+ let inviteGroup;
+ let panel;
- var filesHighlight;
- var membersHighlight;
- var notificationsHighlight;
+ let filesHighlight;
+ let membersHighlight;
+ let notificationsHighlight;
if (!this.props.collapsed) {
- if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) {
+ if (this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo) {
membersHighlight =
;
}
else if (this.state.phase == this.Phase.FilePanel) {
@@ -162,11 +193,11 @@ module.exports = React.createClass({
}
}
- var membersBadge;
- if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) {
- var cli = MatrixClientPeg.get();
- var room = cli.getRoom(this.props.roomId);
- var user_is_in_room;
+ let membersBadge;
+ if ((this.state.phase == this.Phase.RoomMemberList || this.state.phase === this.Phase.RoomMemberInfo) && this.props.roomId) {
+ const cli = MatrixClientPeg.get();
+ const room = cli.getRoom(this.props.roomId);
+ let user_is_in_room;
if (room) {
membersBadge = room.getJoinedMembers().length;
user_is_in_room = room.hasMembershipState(
@@ -186,48 +217,72 @@ module.exports = React.createClass({
}
+ let headerButtons = [];
if (this.props.roomId) {
- buttonGroup =
-
-
- { membersBadge ? membersBadge : }
-
- { membersHighlight }
-
-
-
-
- { filesHighlight }
-
-
-
-
- { notificationsHighlight }
-
-
-
-
-
;
+ headerButtons.push(
+
+ { membersBadge ? membersBadge : }
+
+ { membersHighlight }
+
+ );
+ headerButtons.push(
+
+
+
+ { filesHighlight }
+
+ );
+ headerButtons.push(
+
+
+
+ { notificationsHighlight }
+
+ );
+ }
+
+ 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(
+
+
+
+ );
}
if (!this.props.collapsed) {
- if(this.props.roomId && this.state.phase == this.Phase.MemberList) {
+ if (this.props.roomId && this.state.phase == this.Phase.RoomMemberList) {
panel =
- }
- else if(this.state.phase == this.Phase.MemberInfo) {
- var MemberInfo = sdk.getComponent('rooms.MemberInfo');
+ } else if (this.props.groupId && this.state.phase == this.Phase.GroupMemberList) {
+ panel = ;
+ inviteGroup = (
+
+
+
+
+ { _t('Invite to this group') }
+
+ );
+ } else if (this.state.phase == this.Phase.RoomMemberInfo) {
+ const MemberInfo = sdk.getComponent('rooms.MemberInfo');
panel =
- }
- else if (this.state.phase == this.Phase.NotificationPanel) {
- panel =
- }
- else if (this.state.phase == this.Phase.FilePanel) {
- panel =
+ } else if (this.state.phase == this.Phase.GroupMemberInfo) {
+ const GroupMemberInfo = sdk.getComponent('groups.GroupMemberInfo');
+ panel = ;
+ } else if (this.state.phase == this.Phase.NotificationPanel) {
+ panel = ;
+ } else if (this.state.phase == this.Phase.FilePanel) {
+ panel = ;
}
}
@@ -235,7 +290,7 @@ module.exports = React.createClass({
panel =
;
}
- var classes = "mx_RightPanel mx_fadable";
+ let classes = "mx_RightPanel mx_fadable";
if (this.props.collapsed) {
classes += " collapsed";
}
@@ -243,7 +298,9 @@ module.exports = React.createClass({
return (
- { buttonGroup }
+
+ {headerButtons}
+
{ panel }
diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js
index 9e10c012..73e2aeba 100644
--- a/src/components/structures/RoomSubList.js
+++ b/src/components/structures/RoomSubList.js
@@ -88,6 +88,7 @@ var RoomSubList = React.createClass({
searchFilter: React.PropTypes.string,
emptyContent: React.PropTypes.node, // content shown if the list is empty
headerItems: React.PropTypes.node, // content shown in the sublist header
+ extraTiles: React.PropTypes.arrayOf(React.PropTypes.node), // extra elements added beneath tiles
},
getInitialState: function() {
@@ -102,6 +103,7 @@ var RoomSubList = React.createClass({
return {
onHeaderClick: function() {}, // NOP
onShowMoreRooms: function() {}, // NOP
+ extraTiles: [],
isInvite: false,
};
},
@@ -534,13 +536,14 @@ var RoomSubList = React.createClass({
var label = this.props.collapsed ? null : this.props.label;
let content;
- if (this.state.sortedList.length == 0 && !this.props.searchFilter) {
+ if (this.state.sortedList.length == 0 && !this.props.searchFilter && !this.props.extraTiles) {
content = this.props.emptyContent;
} else {
content = this.makeRoomTiles();
+ content.push(...this.props.extraTiles);
}
- if (this.state.sortedList.length > 0 || this.props.editable) {
+ if (this.state.sortedList.length > 0 || this.props.extraTiles.length > 0 || this.props.editable) {
var subList;
var classes = "mx_RoomSubList";
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 4f4a788b..c933b619 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -217,5 +217,6 @@
"Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
"To return to your account in future you need to set a password ": "To return to your account in future you need to set a password ",
"Set Password": "Set Password",
- "Couldn't load home page": "Couldn't load home page"
+ "Couldn't load home page": "Couldn't load home page",
+ "Invite to this group": "Invite to this group"
}
diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss
index 8caf15ca..61bfa104 100644
--- a/src/skins/vector/css/_components.scss
+++ b/src/skins/vector/css/_components.scss
@@ -32,6 +32,7 @@
@import "./matrix-react-sdk/views/elements/_ProgressBar.scss";
@import "./matrix-react-sdk/views/elements/_RichText.scss";
@import "./matrix-react-sdk/views/elements/_RoleButton.scss";
+@import "./matrix-react-sdk/views/groups/_GroupInviteTile.scss";
@import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
@import "./matrix-react-sdk/views/login/_ServerConfig.scss";
@import "./matrix-react-sdk/views/messages/_MEmoteBody.scss";
diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
index ae9f97b2..f9932f6f 100644
--- a/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
+++ b/src/skins/vector/css/matrix-react-sdk/structures/_GroupView.scss
@@ -70,8 +70,16 @@ limitations under the License.
flex: 1;
}
-.mx_GroupView_saveButton, .mx_GroupView_cancelButton {
- display: table-cell;
+.mx_GroupView_header_rightCol {
+ display: flex;
+}
+
+.mx_GroupView_membership_buttonContainer {
+ margin-top: 10px;
+}
+
+.mx_GroupView_textButton {
+ display: inline-block;
}
.mx_GroupView_header_groupid {
@@ -126,6 +134,16 @@ limitations under the License.
top: 5px;
}
+.mx_GroupView_invitedSection {
+ width: 70%;
+ padding: 20px;
+ border: 1px solid $group-alert-color;
+ margin-left: auto;
+ margin-right: auto;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
.mx_GroupView_featuredThings {
margin-top: 20px;
}
diff --git a/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss
new file mode 100644
index 00000000..6b4034b9
--- /dev/null
+++ b/src/skins/vector/css/matrix-react-sdk/views/groups/_GroupInviteTile.scss
@@ -0,0 +1,74 @@
+/*
+Copyright 2017 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.
+*/
+
+.mx_GroupInviteTile {
+ position: relative;
+ cursor: pointer;
+ font-size: 13px;
+ display: block;
+ height: 34px;
+}
+
+.mx_GroupInviteTile_nameContainer {
+ display: inline-block;
+ width: 180px;
+ height: 24px;
+}
+
+.mx_GroupInviteTile_avatarContainer {
+ display: inline-block;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 16px;
+ padding-right: 6px;
+ width: 24px;
+ height: 24px;
+ vertical-align: middle;
+}
+
+.mx_GroupInviteTile_name {
+ display: inline-block;
+ position: relative;
+ width: 165px;
+ vertical-align: middle;
+ padding-left: 6px;
+ padding-right: 6px;
+ padding-top: 2px;
+ padding-bottom: 3px;
+ color: $roomtile-name-color;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.mx_GroupInviteTile_badge {
+ display: inline-block;
+ min-width: 15px;
+ height: 15px;
+ position: absolute;
+ right: 8px; /*gutter */
+ top: 9px;
+ border-radius: 8px;
+ color: $accent-fg-color;
+ background-color: $group-alert-color;
+ font-weight: 600;
+ font-size: 10px;
+ text-align: center;
+ padding-top: 1px;
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
diff --git a/src/skins/vector/css/themes/_base.scss b/src/skins/vector/css/themes/_base.scss
index 6f613e38..a13c517b 100644
--- a/src/skins/vector/css/themes/_base.scss
+++ b/src/skins/vector/css/themes/_base.scss
@@ -22,6 +22,8 @@ $warning-color: #ff0064;
$mention-user-pill-bg-color: #ff0064;
$other-user-pill-bg-color: rgba(0, 0, 0, 0.1);
+$group-alert-color: #774f7e;
+
$preview-bar-bg-color: #f7f7f7;
// left-panel style muted accent color
diff --git a/test/app-tests/joining.js b/test/app-tests/joining.js
index 9e0a84c7..29da3413 100644
--- a/test/app-tests/joining.js
+++ b/test/app-tests/joining.js
@@ -176,8 +176,9 @@ describe('joining a room', function () {
return Promise.delay(1);
}).then(() => {
- // We've joined, expect this to false
- expect(roomView.state.joining).toBe(false);
+ // NB. we don't expect the 'joining' flag to reset at any point:
+ // it will stay set and we observe whether we have Room object for
+ // the room and whether our member event shows we're joined.
// now send the room down the /sync pipe
httpBackend.when('GET', '/sync').