Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/hide-join-part-2
This commit is contained in:
382
src/components/structures/GroupView.js
Normal file
382
src/components/structures/GroupView.js
Normal file
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
||||
import { _t } from '../../languageHandler';
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
|
||||
const RoomSummaryType = PropTypes.shape({
|
||||
room_id: PropTypes.string.isRequired,
|
||||
profile: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
avatar_url: PropTypes.string,
|
||||
canonical_alias: PropTypes.string,
|
||||
}).isRequired,
|
||||
});
|
||||
|
||||
const UserSummaryType = PropTypes.shape({
|
||||
summaryInfo: PropTypes.shape({
|
||||
user_id: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
});
|
||||
|
||||
const CategoryRoomList = React.createClass({
|
||||
displayName: 'CategoryRoomList',
|
||||
|
||||
props: {
|
||||
rooms: PropTypes.arrayOf(RoomSummaryType).isRequired,
|
||||
category: PropTypes.shape({
|
||||
profile: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
}).isRequired,
|
||||
}),
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const roomNodes = this.props.rooms.map((r) => {
|
||||
return <FeaturedRoom key={r.room_id} summaryInfo={r} />;
|
||||
});
|
||||
let catHeader = null;
|
||||
if (this.props.category && this.props.category.profile) {
|
||||
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>;
|
||||
}
|
||||
return <div>
|
||||
{catHeader}
|
||||
{roomNodes}
|
||||
</div>;
|
||||
},
|
||||
});
|
||||
|
||||
const FeaturedRoom = React.createClass({
|
||||
displayName: 'FeaturedRoom',
|
||||
|
||||
props: {
|
||||
summaryInfo: RoomSummaryType.isRequired,
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_alias: this.props.summaryInfo.profile.canonical_alias,
|
||||
room_id: this.props.summaryInfo.room_id,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
|
||||
|
||||
const oobData = {
|
||||
roomId: this.props.summaryInfo.room_id,
|
||||
avatarUrl: this.props.summaryInfo.profile.avatar_url,
|
||||
name: this.props.summaryInfo.profile.name,
|
||||
};
|
||||
let permalink = null;
|
||||
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
|
||||
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
|
||||
}
|
||||
let roomNameNode = null;
|
||||
if (permalink) {
|
||||
roomNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.profile.name}</a>;
|
||||
} else {
|
||||
roomNameNode = <span>{this.props.summaryInfo.profile.name}</span>;
|
||||
}
|
||||
|
||||
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
|
||||
<RoomAvatar oobData={oobData} width={64} height={64} />
|
||||
<div className="mx_GroupView_featuredThing_name">{roomNameNode}</div>
|
||||
</AccessibleButton>;
|
||||
},
|
||||
});
|
||||
|
||||
const RoleUserList = React.createClass({
|
||||
displayName: 'RoleUserList',
|
||||
|
||||
props: {
|
||||
users: PropTypes.arrayOf(UserSummaryType).isRequired,
|
||||
role: PropTypes.shape({
|
||||
profile: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
}).isRequired,
|
||||
}),
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const userNodes = this.props.users.map((u) => {
|
||||
return <FeaturedUser key={u.user_id} summaryInfo={u} />;
|
||||
});
|
||||
let roleHeader = null;
|
||||
if (this.props.role && this.props.role.profile) {
|
||||
roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>;
|
||||
}
|
||||
return <div>
|
||||
{roleHeader}
|
||||
{userNodes}
|
||||
</div>;
|
||||
},
|
||||
});
|
||||
|
||||
const FeaturedUser = React.createClass({
|
||||
displayName: 'FeaturedUser',
|
||||
|
||||
props: {
|
||||
summaryInfo: UserSummaryType.isRequired,
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_start_chat_or_reuse',
|
||||
user_id: this.props.summaryInfo.user_id,
|
||||
go_home_on_cancel: false,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// Add avatar once we get profile info inline in the summary response
|
||||
//const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
|
||||
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
|
||||
const userNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.user_id}</a>;
|
||||
|
||||
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
|
||||
<div className="mx_GroupView_featuredThing_name">{userNameNode}</div>
|
||||
</AccessibleButton>;
|
||||
},
|
||||
});
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'GroupView',
|
||||
|
||||
propTypes: {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
summary: null,
|
||||
error: null,
|
||||
editing: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._loadGroupFromServer(this.props.groupId);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
if (this.props.groupId != newProps.groupId) {
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: null,
|
||||
}, () => {
|
||||
this._loadGroupFromServer(newProps.groupId);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_loadGroupFromServer: function(groupId) {
|
||||
MatrixClientPeg.get().getGroupSummary(groupId).done((res) => {
|
||||
this.setState({
|
||||
summary: res,
|
||||
error: null,
|
||||
});
|
||||
}, (err) => {
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onSettingsClick: function() {
|
||||
this.setState({editing: true});
|
||||
},
|
||||
|
||||
_getFeaturedRoomsNode() {
|
||||
const summary = this.state.summary;
|
||||
|
||||
if (summary.rooms_section.rooms.length == 0) return null;
|
||||
|
||||
const defaultCategoryRooms = [];
|
||||
const categoryRooms = {};
|
||||
summary.rooms_section.rooms.forEach((r) => {
|
||||
if (r.category_id === null) {
|
||||
defaultCategoryRooms.push(r);
|
||||
} else {
|
||||
let list = categoryRooms[r.category_id];
|
||||
if (list === undefined) {
|
||||
list = [];
|
||||
categoryRooms[r.category_id] = list;
|
||||
}
|
||||
list.push(r);
|
||||
}
|
||||
});
|
||||
|
||||
let defaultCategoryNode = null;
|
||||
if (defaultCategoryRooms.length > 0) {
|
||||
defaultCategoryNode = <CategoryRoomList rooms={defaultCategoryRooms} />;
|
||||
}
|
||||
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
|
||||
const cat = summary.rooms_section.categories[catId];
|
||||
return <CategoryRoomList key={catId} rooms={categoryRooms[catId]} category={cat} />;
|
||||
});
|
||||
|
||||
return <div className="mx_GroupView_featuredThings">
|
||||
<div className="mx_GroupView_featuredThings_header">
|
||||
{_t('Featured Rooms:')}
|
||||
</div>
|
||||
{defaultCategoryNode}
|
||||
{categoryRoomNodes}
|
||||
</div>;
|
||||
},
|
||||
|
||||
_getFeaturedUsersNode() {
|
||||
const summary = this.state.summary;
|
||||
|
||||
if (summary.users_section.users.length == 0) return null;
|
||||
|
||||
const noRoleUsers = [];
|
||||
const roleUsers = {};
|
||||
summary.users_section.users.forEach((u) => {
|
||||
if (u.role_id === null) {
|
||||
noRoleUsers.push(u);
|
||||
} else {
|
||||
let list = roleUsers[u.role_id];
|
||||
if (list === undefined) {
|
||||
list = [];
|
||||
roleUsers[u.role_id] = list;
|
||||
}
|
||||
list.push(u);
|
||||
}
|
||||
});
|
||||
|
||||
let noRoleNode = null;
|
||||
if (noRoleUsers.length > 0) {
|
||||
noRoleNode = <RoleUserList users={noRoleUsers} />;
|
||||
}
|
||||
const roleUserNodes = Object.keys(roleUsers).map((roleId) => {
|
||||
const role = summary.users_section.roles[roleId];
|
||||
return <RoleUserList key={roleId} users={roleUsers[roleId]} role={role} />;
|
||||
});
|
||||
|
||||
return <div className="mx_GroupView_featuredThings">
|
||||
<div className="mx_GroupView_featuredThings_header">
|
||||
{_t('Featured Users:')}
|
||||
</div>
|
||||
{noRoleNode}
|
||||
{roleUserNodes}
|
||||
</div>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
if (this.state.summary === null && this.state.error === null) {
|
||||
return <Loader />;
|
||||
} else if (this.state.editing) {
|
||||
return <div />;
|
||||
} else if (this.state.summary) {
|
||||
const summary = this.state.summary;
|
||||
let description = null;
|
||||
if (summary.profile && summary.profile.long_description) {
|
||||
description = sanitizedHtmlNode(summary.profile.long_description);
|
||||
}
|
||||
|
||||
const roomBody = <div>
|
||||
<div className="mx_GroupView_groupDesc">{description}</div>
|
||||
{this._getFeaturedRoomsNode()}
|
||||
{this._getFeaturedUsersNode()}
|
||||
</div>;
|
||||
|
||||
let nameNode;
|
||||
if (summary.profile && summary.profile.name) {
|
||||
nameNode = <div className="mx_RoomHeader_name">
|
||||
<span>{summary.profile.name}</span>
|
||||
<span className="mx_GroupView_header_groupid">
|
||||
({this.props.groupId})
|
||||
</span>
|
||||
</div>;
|
||||
} else {
|
||||
nameNode = <div className="mx_RoomHeader_name">
|
||||
<span>{this.props.groupId}</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||
|
||||
// settings button is display: none until settings is wired up
|
||||
return (
|
||||
<div className="mx_GroupView">
|
||||
<div className="mx_RoomHeader">
|
||||
<div className="mx_RoomHeader_wrapper">
|
||||
<div className="mx_RoomHeader_avatar">
|
||||
<GroupAvatar
|
||||
groupId={this.props.groupId}
|
||||
groupAvatarUrl={groupAvatarUrl}
|
||||
width={48} height={48}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_RoomHeader_info">
|
||||
{nameNode}
|
||||
<div className="mx_RoomHeader_topic">
|
||||
{summary.profile.short_description}
|
||||
</div>
|
||||
</div>
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this._onSettingsClick} title={_t("Settings")} style={{display: 'none'}}>
|
||||
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
{roomBody}
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.error) {
|
||||
if (this.state.error.httpStatus === 404) {
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
Group {this.props.groupId} not found
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let extraText;
|
||||
if (this.state.error.errcode === 'M_UNRECOGNIZED') {
|
||||
extraText = <div>{_t('This Home server does not support groups')}</div>;
|
||||
}
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
Failed to load {this.props.groupId}
|
||||
{extraText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error("Invalid state for GroupView");
|
||||
return <div />;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -156,13 +156,20 @@ export default React.createClass({
|
||||
}
|
||||
*/
|
||||
|
||||
var handled = false;
|
||||
let handled = false;
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
let ctrlCmdOnly;
|
||||
if (isMac) {
|
||||
ctrlCmdOnly = ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
||||
} else {
|
||||
ctrlCmdOnly = ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
|
||||
}
|
||||
|
||||
switch (ev.keyCode) {
|
||||
case KeyCode.UP:
|
||||
case KeyCode.DOWN:
|
||||
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
var action = ev.keyCode == KeyCode.UP ?
|
||||
let action = ev.keyCode == KeyCode.UP ?
|
||||
'view_prev_room' : 'view_next_room';
|
||||
dis.dispatch({action: action});
|
||||
handled = true;
|
||||
@@ -184,6 +191,14 @@ export default React.createClass({
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
case KeyCode.KEY_K:
|
||||
if (ctrlCmdOnly) {
|
||||
dis.dispatch({
|
||||
action: 'focus_room_filter',
|
||||
});
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
@@ -210,8 +225,11 @@ export default React.createClass({
|
||||
const CreateRoom = sdk.getComponent('structures.CreateRoom');
|
||||
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||
const HomePage = sdk.getComponent('structures.HomePage');
|
||||
const GroupView = sdk.getComponent('structures.GroupView');
|
||||
const MyGroups = sdk.getComponent('structures.MyGroups');
|
||||
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
|
||||
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
|
||||
|
||||
let page_element;
|
||||
@@ -239,7 +257,6 @@ export default React.createClass({
|
||||
page_element = <UserSettings
|
||||
onClose={this.props.onUserSettingsClose}
|
||||
brand={this.props.config.brand}
|
||||
collapsedRhs={this.props.collapse_rhs}
|
||||
enableLabs={this.props.config.enableLabs}
|
||||
referralBaseUrl={this.props.config.referralBaseUrl}
|
||||
teamToken={this.props.teamToken}
|
||||
@@ -247,6 +264,10 @@ export default React.createClass({
|
||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
|
||||
break;
|
||||
|
||||
case PageTypes.MyGroups:
|
||||
page_element = <MyGroups />;
|
||||
break;
|
||||
|
||||
case PageTypes.CreateRoom:
|
||||
page_element = <CreateRoom
|
||||
onRoomCreated={this.props.onRoomCreated}
|
||||
@@ -263,32 +284,40 @@ export default React.createClass({
|
||||
break;
|
||||
|
||||
case PageTypes.HomePage:
|
||||
// If team server config is present, pass the teamServerURL. props.teamToken
|
||||
// must also be set for the team page to be displayed, otherwise the
|
||||
// welcomePageUrl is used (which might be undefined).
|
||||
const teamServerUrl = this.props.config.teamServerConfig ?
|
||||
this.props.config.teamServerConfig.teamServerURL : null;
|
||||
{
|
||||
// If team server config is present, pass the teamServerURL. props.teamToken
|
||||
// must also be set for the team page to be displayed, otherwise the
|
||||
// welcomePageUrl is used (which might be undefined).
|
||||
const teamServerUrl = this.props.config.teamServerConfig ?
|
||||
this.props.config.teamServerConfig.teamServerURL : null;
|
||||
|
||||
page_element = <HomePage
|
||||
collapsedRhs={this.props.collapse_rhs}
|
||||
teamServerUrl={teamServerUrl}
|
||||
teamToken={this.props.teamToken}
|
||||
homePageUrl={this.props.config.welcomePageUrl}
|
||||
/>;
|
||||
page_element = <HomePage
|
||||
teamServerUrl={teamServerUrl}
|
||||
teamToken={this.props.teamToken}
|
||||
homePageUrl={this.props.config.welcomePageUrl}
|
||||
/>;
|
||||
}
|
||||
break;
|
||||
|
||||
case PageTypes.UserView:
|
||||
page_element = null; // deliberately null for now
|
||||
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
|
||||
break;
|
||||
case PageTypes.GroupView:
|
||||
page_element = <GroupView
|
||||
groupId={this.props.currentGroupId}
|
||||
/>;
|
||||
break;
|
||||
}
|
||||
|
||||
let topBar;
|
||||
const isGuest = this.props.matrixClient.isGuest();
|
||||
var topBar;
|
||||
if (this.props.hasNewVersion) {
|
||||
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
|
||||
releaseNotes={this.props.newVersionReleaseNotes}
|
||||
releaseNotes={this.props.newVersionReleaseNotes}
|
||||
/>;
|
||||
} else if (this.props.checkingForUpdate) {
|
||||
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
|
||||
} else if (this.state.userHasGeneratedPassword) {
|
||||
topBar = <PasswordNagBar />;
|
||||
} else if (!isGuest && Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
|
||||
|
||||
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import q from 'q';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import React from 'react';
|
||||
import Matrix from "matrix-js-sdk";
|
||||
@@ -41,9 +41,44 @@ import PageTypes from '../../PageTypes';
|
||||
|
||||
import createRoom from "../../createRoom";
|
||||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||
import KeyRequestHandler from '../../KeyRequestHandler';
|
||||
import { _t, getCurrentLanguage } from '../../languageHandler';
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
const VIEWS = {
|
||||
// a special initial state which is only used at startup, while we are
|
||||
// trying to re-animate a matrix client or register as a guest.
|
||||
LOADING: 0,
|
||||
|
||||
// we are showing the login view
|
||||
LOGIN: 1,
|
||||
|
||||
// we are showing the registration view
|
||||
REGISTER: 2,
|
||||
|
||||
// completeing the registration flow
|
||||
POST_REGISTRATION: 3,
|
||||
|
||||
// showing the 'forgot password' view
|
||||
FORGOT_PASSWORD: 4,
|
||||
|
||||
// we have valid matrix credentials (either via an explicit login, via the
|
||||
// initial re-animation/guest registration, or via a registration), and are
|
||||
// now setting up a matrixclient to talk to it. This isn't an instant
|
||||
// process because (a) we need to clear out indexeddb, and (b) we need to
|
||||
// talk to the team server; while it is going on we show a big spinner.
|
||||
LOGGING_IN: 5,
|
||||
|
||||
// we are logged in with an active matrix client.
|
||||
LOGGED_IN: 6,
|
||||
};
|
||||
|
||||
module.exports = React.createClass({
|
||||
// we export this so that the integration tests can use it :-S
|
||||
statics: {
|
||||
VIEWS: VIEWS,
|
||||
},
|
||||
|
||||
displayName: 'MatrixChat',
|
||||
|
||||
propTypes: {
|
||||
@@ -59,8 +94,8 @@ module.exports = React.createClass({
|
||||
// the initial queryParams extracted from the hash-fragment of the URI
|
||||
startingFragmentQueryParams: React.PropTypes.object,
|
||||
|
||||
// called when the session load completes
|
||||
onLoadCompleted: React.PropTypes.func,
|
||||
// called when we have completed a token login
|
||||
onTokenLoginCompleted: React.PropTypes.func,
|
||||
|
||||
// Represents the screen to display as a result of parsing the initial
|
||||
// window.location
|
||||
@@ -93,14 +128,11 @@ module.exports = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
const s = {
|
||||
loading: true,
|
||||
screen: undefined,
|
||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||
// the master view we are showing.
|
||||
view: VIEWS.LOADING,
|
||||
|
||||
// Stashed guest credentials if the user logs out
|
||||
// whilst logged in as a guest user (so they can change
|
||||
// their mind & log back in)
|
||||
guestCreds: null,
|
||||
// a thing to call showScreen with once login completes.
|
||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||
|
||||
// What the LoggedInView would be showing if visible
|
||||
page_type: null,
|
||||
@@ -113,8 +145,6 @@ module.exports = React.createClass({
|
||||
// If we're trying to just view a user ID (i.e. /user URL), this is it
|
||||
viewUserId: null,
|
||||
|
||||
loggedIn: false,
|
||||
loggingIn: false,
|
||||
collapse_lhs: false,
|
||||
collapse_rhs: false,
|
||||
ready: false,
|
||||
@@ -127,6 +157,7 @@ module.exports = React.createClass({
|
||||
newVersion: null,
|
||||
hasNewVersion: false,
|
||||
newVersionReleaseNotes: null,
|
||||
checkingForUpdate: null,
|
||||
|
||||
// Parameters used in the registration dance with the IS
|
||||
register_client_secret: null,
|
||||
@@ -143,7 +174,7 @@ module.exports = React.createClass({
|
||||
realQueryParams: {},
|
||||
startingFragmentQueryParams: {},
|
||||
config: {},
|
||||
onLoadCompleted: () => {},
|
||||
onTokenLoginCompleted: () => {},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -193,7 +224,7 @@ module.exports = React.createClass({
|
||||
|
||||
// Used by _viewRoom before getting state from sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = q.defer();
|
||||
this.firstSyncPromise = Promise.defer();
|
||||
|
||||
if (this.props.config.sync_timeline_limit) {
|
||||
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
|
||||
@@ -259,6 +290,9 @@ module.exports = React.createClass({
|
||||
if (this.onUserClick) {
|
||||
linkifyMatrix.onUserClick = this.onUserClick;
|
||||
}
|
||||
if (this.onGroupClick) {
|
||||
linkifyMatrix.onGroupClick = this.onGroupClick;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
this.handleResize();
|
||||
@@ -266,39 +300,49 @@ module.exports = React.createClass({
|
||||
const teamServerConfig = this.props.config.teamServerConfig || {};
|
||||
Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
|
||||
|
||||
// if the user has followed a login or register link, don't reanimate
|
||||
// the old creds, but rather go straight to the relevant page
|
||||
// the first thing to do is to try the token params in the query-string
|
||||
Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
|
||||
if(loggedIn) {
|
||||
this.props.onTokenLoginCompleted();
|
||||
|
||||
const firstScreen = this.state.screenAfterLogin ?
|
||||
this.state.screenAfterLogin.screen : null;
|
||||
// don't do anything else until the page reloads - just stay in
|
||||
// the 'loading' state.
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstScreen === 'login' ||
|
||||
firstScreen === 'register' ||
|
||||
firstScreen === 'forgot_password') {
|
||||
this.props.onLoadCompleted();
|
||||
this.setState({loading: false});
|
||||
this._showScreenAfterLogin();
|
||||
return;
|
||||
}
|
||||
// if the user has followed a login or register link, don't reanimate
|
||||
// the old creds, but rather go straight to the relevant page
|
||||
const firstScreen = this.state.screenAfterLogin ?
|
||||
this.state.screenAfterLogin.screen : null;
|
||||
|
||||
// the extra q() ensures that synchronous exceptions hit the same codepath as
|
||||
// asynchronous ones.
|
||||
q().then(() => {
|
||||
return Lifecycle.loadSession({
|
||||
realQueryParams: this.props.realQueryParams,
|
||||
fragmentQueryParams: this.props.startingFragmentQueryParams,
|
||||
enableGuest: this.props.enableGuest,
|
||||
guestHsUrl: this.getCurrentHsUrl(),
|
||||
guestIsUrl: this.getCurrentIsUrl(),
|
||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||
if (firstScreen === 'login' ||
|
||||
firstScreen === 'register' ||
|
||||
firstScreen === 'forgot_password') {
|
||||
this.setState({loading: false});
|
||||
this._showScreenAfterLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
// the extra Promise.resolve() ensures that synchronous exceptions hit the same codepath as
|
||||
// asynchronous ones.
|
||||
return Promise.resolve().then(() => {
|
||||
return Lifecycle.loadSession({
|
||||
fragmentQueryParams: this.props.startingFragmentQueryParams,
|
||||
enableGuest: this.props.enableGuest,
|
||||
guestHsUrl: this.getCurrentHsUrl(),
|
||||
guestIsUrl: this.getCurrentIsUrl(),
|
||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error(`Error attempting to load session: ${e}`);
|
||||
return false;
|
||||
}).then((loadedSession) => {
|
||||
if (!loadedSession) {
|
||||
// fall back to showing the login screen
|
||||
dis.dispatch({action: "start_login"});
|
||||
}
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error("Unable to load session", e);
|
||||
}).done(()=>{
|
||||
// stuff this through the dispatcher so that it happens
|
||||
// after the on_logged_in action.
|
||||
dis.dispatch({action: 'load_completed'});
|
||||
});
|
||||
}).done();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
@@ -317,18 +361,19 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
setStateForNewScreen: function(state) {
|
||||
setStateForNewView: function(state) {
|
||||
if (state.view === undefined) {
|
||||
throw new Error("setStateForNewView with no view!");
|
||||
}
|
||||
const newState = {
|
||||
screen: undefined,
|
||||
viewUserId: null,
|
||||
loggedIn: false,
|
||||
ready: false,
|
||||
};
|
||||
Object.assign(newState, state);
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
@@ -340,26 +385,19 @@ module.exports = React.createClass({
|
||||
this._startRegistration(payload.params || {});
|
||||
break;
|
||||
case 'start_login':
|
||||
if (MatrixClientPeg.get() &&
|
||||
MatrixClientPeg.get().isGuest()
|
||||
) {
|
||||
this.setState({
|
||||
guestCreds: MatrixClientPeg.getCredentials(),
|
||||
});
|
||||
}
|
||||
this.setStateForNewScreen({
|
||||
screen: 'login',
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGIN,
|
||||
});
|
||||
this.notifyNewScreen('login');
|
||||
break;
|
||||
case 'start_post_registration':
|
||||
this.setState({ // don't clobber loggedIn status
|
||||
screen: 'post_registration',
|
||||
this.setState({
|
||||
view: VIEWS.POST_REGISTRATION,
|
||||
});
|
||||
break;
|
||||
case 'start_password_recovery':
|
||||
this.setStateForNewScreen({
|
||||
screen: 'forgot_password',
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.FORGOT_PASSWORD,
|
||||
});
|
||||
this.notifyNewScreen('forgot_password');
|
||||
break;
|
||||
@@ -383,7 +421,7 @@ module.exports = React.createClass({
|
||||
|
||||
MatrixClientPeg.get().leave(payload.room_id).done(() => {
|
||||
modal.close();
|
||||
if (this.currentRoomId === payload.room_id) {
|
||||
if (this.state.currentRoomId === payload.room_id) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}
|
||||
}, (err) => {
|
||||
@@ -448,6 +486,18 @@ module.exports = React.createClass({
|
||||
this._setPage(PageTypes.RoomDirectory);
|
||||
this.notifyNewScreen('directory');
|
||||
break;
|
||||
case 'view_my_groups':
|
||||
this._setPage(PageTypes.MyGroups);
|
||||
this.notifyNewScreen('groups');
|
||||
break;
|
||||
case 'view_group':
|
||||
{
|
||||
const groupId = payload.group_id;
|
||||
this.setState({currentGroupId: groupId});
|
||||
this._setPage(PageTypes.GroupView);
|
||||
this.notifyNewScreen('group/' + groupId);
|
||||
}
|
||||
break;
|
||||
case 'view_home_page':
|
||||
this._setPage(PageTypes.HomePage);
|
||||
this.notifyNewScreen('home');
|
||||
@@ -456,7 +506,7 @@ module.exports = React.createClass({
|
||||
this._setMxId(payload);
|
||||
break;
|
||||
case 'view_start_chat_or_reuse':
|
||||
this._chatCreateOrReuse(payload.user_id);
|
||||
this._chatCreateOrReuse(payload.user_id, payload.go_home_on_cancel);
|
||||
break;
|
||||
case 'view_create_chat':
|
||||
this._createChat();
|
||||
@@ -500,10 +550,11 @@ module.exports = React.createClass({
|
||||
break;
|
||||
case 'on_logging_in':
|
||||
// We are now logging in, so set the state to reflect that
|
||||
// and also that we're not ready (we'll be marked as logged
|
||||
// in once the login completes, then ready once the sync
|
||||
// completes).
|
||||
this.setState({loggingIn: true, ready: false});
|
||||
// NB. This does not touch 'ready' since if our dispatches
|
||||
// are delayed, the sync could already have completed
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGGING_IN,
|
||||
});
|
||||
break;
|
||||
case 'on_logged_in':
|
||||
this._onLoggedIn(payload.teamToken);
|
||||
@@ -512,10 +563,12 @@ module.exports = React.createClass({
|
||||
this._onLoggedOut();
|
||||
break;
|
||||
case 'will_start_client':
|
||||
this._onWillStartClient();
|
||||
break;
|
||||
case 'load_completed':
|
||||
this._onLoadCompleted();
|
||||
this.setState({ready: false}, () => {
|
||||
// if the client is about to start, we are, by definition, not ready.
|
||||
// Set ready to false now, then it'll be set to true when the sync
|
||||
// listener we set below fires.
|
||||
this._onWillStartClient();
|
||||
});
|
||||
break;
|
||||
case 'new_version':
|
||||
this.onVersion(
|
||||
@@ -523,6 +576,12 @@ module.exports = React.createClass({
|
||||
payload.releaseNotes,
|
||||
);
|
||||
break;
|
||||
case 'check_updates':
|
||||
this.setState({ checkingForUpdate: payload.value });
|
||||
break;
|
||||
case 'send_event':
|
||||
this.onSendEvent(payload.room_id, payload.event);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -537,8 +596,8 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
_startRegistration: function(params) {
|
||||
this.setStateForNewScreen({
|
||||
screen: 'register',
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.REGISTER,
|
||||
// these params may be undefined, but if they are,
|
||||
// unset them from our state: we don't want to
|
||||
// resume a previous registration session if the
|
||||
@@ -635,7 +694,7 @@ module.exports = React.createClass({
|
||||
|
||||
// Wait for the first sync to complete so that if a room does have an alias,
|
||||
// it would have been retrieved.
|
||||
let waitFor = q(null);
|
||||
let waitFor = Promise.resolve(null);
|
||||
if (!this.firstSyncComplete) {
|
||||
if (!this.firstSyncPromise) {
|
||||
console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id);
|
||||
@@ -742,7 +801,9 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
_chatCreateOrReuse: function(userId) {
|
||||
_chatCreateOrReuse: function(userId, goHomeOnCancel) {
|
||||
if (goHomeOnCancel === undefined) goHomeOnCancel = true;
|
||||
|
||||
const ChatCreateOrReuseDialog = sdk.getComponent(
|
||||
'views.dialogs.ChatCreateOrReuseDialog',
|
||||
);
|
||||
@@ -773,7 +834,7 @@ module.exports = React.createClass({
|
||||
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
||||
userId: userId,
|
||||
onFinished: (success) => {
|
||||
if (!success) {
|
||||
if (!success && goHomeOnCancel) {
|
||||
// Dialog cancelled, default to home
|
||||
dis.dispatch({ action: 'view_home_page' });
|
||||
}
|
||||
@@ -846,14 +907,6 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the sessionloader has finished
|
||||
*/
|
||||
_onLoadCompleted: function() {
|
||||
this.props.onLoadCompleted();
|
||||
this.setState({loading: false});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever someone changes the theme
|
||||
*
|
||||
@@ -906,9 +959,7 @@ module.exports = React.createClass({
|
||||
*/
|
||||
_onLoggedIn: function(teamToken) {
|
||||
this.setState({
|
||||
guestCreds: null,
|
||||
loggedIn: true,
|
||||
loggingIn: false,
|
||||
view: VIEWS.LOGGED_IN,
|
||||
});
|
||||
|
||||
if (teamToken) {
|
||||
@@ -917,10 +968,6 @@ module.exports = React.createClass({
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
} else if (this._is_registered) {
|
||||
this._is_registered = false;
|
||||
// reset the 'have completed first sync' flag,
|
||||
// since we've just logged in and will be about to sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = q.defer();
|
||||
|
||||
// Set the display name = user ID localpart
|
||||
MatrixClientPeg.get().setDisplayName(
|
||||
@@ -969,8 +1016,8 @@ module.exports = React.createClass({
|
||||
*/
|
||||
_onLoggedOut: function() {
|
||||
this.notifyNewScreen('login');
|
||||
this.setStateForNewScreen({
|
||||
loggedIn: false,
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGIN,
|
||||
ready: false,
|
||||
collapse_lhs: false,
|
||||
collapse_rhs: false,
|
||||
@@ -978,6 +1025,7 @@ module.exports = React.createClass({
|
||||
page_type: PageTypes.RoomDirectory,
|
||||
});
|
||||
this._teamToken = null;
|
||||
this._setPageSubtitle();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -986,6 +1034,12 @@ module.exports = React.createClass({
|
||||
*/
|
||||
_onWillStartClient() {
|
||||
const self = this;
|
||||
|
||||
// reset the 'have completed first sync' flag,
|
||||
// since we're about to start the client and therefore about
|
||||
// to do the first sync
|
||||
this.firstSyncComplete = false;
|
||||
this.firstSyncPromise = Promise.defer();
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
||||
@@ -1056,6 +1110,14 @@ module.exports = React.createClass({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const krh = new KeyRequestHandler(cli);
|
||||
cli.on("crypto.roomKeyRequest", (req) => {
|
||||
krh.handleKeyRequest(req);
|
||||
});
|
||||
cli.on("crypto.roomKeyRequestCancellation", (req) => {
|
||||
krh.handleKeyRequestCancellation(req);
|
||||
});
|
||||
},
|
||||
|
||||
showScreen: function(screen, params) {
|
||||
@@ -1095,6 +1157,10 @@ module.exports = React.createClass({
|
||||
dis.dispatch({
|
||||
action: 'view_room_directory',
|
||||
});
|
||||
} else if (screen == 'groups') {
|
||||
dis.dispatch({
|
||||
action: 'view_my_groups',
|
||||
});
|
||||
} else if (screen == 'post_registration') {
|
||||
dis.dispatch({
|
||||
action: 'start_post_registration',
|
||||
@@ -1133,7 +1199,7 @@ module.exports = React.createClass({
|
||||
|
||||
// we can't view a room unless we're logged in
|
||||
// (a guest account is fine)
|
||||
if (this.state.loggedIn) {
|
||||
if (this.state.view === VIEWS.LOGGED_IN) {
|
||||
dis.dispatch(payload);
|
||||
}
|
||||
} else if (screen.indexOf('user/') == 0) {
|
||||
@@ -1154,6 +1220,15 @@ module.exports = React.createClass({
|
||||
member: member,
|
||||
});
|
||||
}
|
||||
} else if (screen.indexOf('group/') == 0) {
|
||||
const groupId = screen.substring(6);
|
||||
|
||||
// TODO: Check valid group ID
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_group',
|
||||
group_id: groupId,
|
||||
});
|
||||
} else {
|
||||
console.info("Ignoring showScreen for '%s'", screen);
|
||||
}
|
||||
@@ -1182,6 +1257,11 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
onGroupClick: function(event, groupId) {
|
||||
event.preventDefault();
|
||||
dis.dispatch({action: 'view_group', group_id: groupId});
|
||||
},
|
||||
|
||||
onLogoutClick: function(event) {
|
||||
dis.dispatch({
|
||||
action: 'logout',
|
||||
@@ -1231,29 +1311,25 @@ module.exports = React.createClass({
|
||||
this.showScreen("forgot_password");
|
||||
},
|
||||
|
||||
onReturnToGuestClick: function() {
|
||||
// reanimate our guest login
|
||||
if (this.state.guestCreds) {
|
||||
// TODO: this is probably a bit broken - we don't want to be
|
||||
// clearing storage when we reanimate the guest creds.
|
||||
Lifecycle.setLoggedIn(this.state.guestCreds);
|
||||
this.setState({guestCreds: null});
|
||||
}
|
||||
onReturnToAppClick: function() {
|
||||
// treat it the same as if the user had completed the login
|
||||
this._onLoggedIn(null);
|
||||
},
|
||||
|
||||
// returns a promise which resolves to the new MatrixClient
|
||||
onRegistered: function(credentials, teamToken) {
|
||||
// XXX: These both should be in state or ideally store(s) because we risk not
|
||||
// rendering the most up-to-date view of state otherwise.
|
||||
// teamToken may not be truthy
|
||||
this._teamToken = teamToken;
|
||||
this._is_registered = true;
|
||||
Lifecycle.setLoggedIn(credentials);
|
||||
return Lifecycle.setLoggedIn(credentials);
|
||||
},
|
||||
|
||||
onFinishPostRegistration: function() {
|
||||
// Don't confuse this with "PageType" which is the middle window to show
|
||||
this.setState({
|
||||
screen: undefined,
|
||||
view: VIEWS.LOGGED_IN,
|
||||
});
|
||||
this.showScreen("settings");
|
||||
},
|
||||
@@ -1264,9 +1340,35 @@ module.exports = React.createClass({
|
||||
newVersion: latest,
|
||||
hasNewVersion: current !== latest,
|
||||
newVersionReleaseNotes: releaseNotes,
|
||||
checkingForUpdate: null,
|
||||
});
|
||||
},
|
||||
|
||||
onSendEvent: function(roomId, event) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!cli) {
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
return;
|
||||
}
|
||||
|
||||
cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
}, (err) => {
|
||||
if (err.name === 'UnknownDeviceError') {
|
||||
dis.dispatch({
|
||||
action: 'unknown_device_error',
|
||||
err: err,
|
||||
room: cli.getRoom(roomId),
|
||||
});
|
||||
}
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
});
|
||||
},
|
||||
|
||||
_setPageSubtitle: function(subtitle='') {
|
||||
document.title = `Riot ${subtitle}`;
|
||||
},
|
||||
|
||||
updateStatusIndicator: function(state, prevState) {
|
||||
let notifCount = 0;
|
||||
|
||||
@@ -1287,15 +1389,15 @@ module.exports = React.createClass({
|
||||
PlatformPeg.get().setNotificationCount(notifCount);
|
||||
}
|
||||
|
||||
let title = "Riot ";
|
||||
let subtitle = '';
|
||||
if (state === "ERROR") {
|
||||
title += `[${_t("Offline")}] `;
|
||||
subtitle += `[${_t("Offline")}] `;
|
||||
}
|
||||
if (notifCount > 0) {
|
||||
title += `[${notifCount}]`;
|
||||
subtitle += `[${notifCount}]`;
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
this._setPageSubtitle(subtitle);
|
||||
},
|
||||
|
||||
onUserSettingsClose: function() {
|
||||
@@ -1321,11 +1423,9 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// `loading` might be set to false before `loggedIn = true`, causing the default
|
||||
// (`<Login>`) to be visible for a few MS (say, whilst a request is in-flight to
|
||||
// the RTS). So in the meantime, use `loggingIn`, which is true between
|
||||
// actions `on_logging_in` and `on_logged_in`.
|
||||
if (this.state.loading || this.state.loggingIn) {
|
||||
// console.log(`Rendering MatrixChat with view ${this.state.view}`);
|
||||
|
||||
if (this.state.view === VIEWS.LOADING || this.state.view === VIEWS.LOGGING_IN) {
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
@@ -1335,7 +1435,7 @@ module.exports = React.createClass({
|
||||
}
|
||||
|
||||
// needs to be before normal PageTypes as you are logged in technically
|
||||
if (this.state.screen == 'post_registration') {
|
||||
if (this.state.view === VIEWS.POST_REGISTRATION) {
|
||||
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
|
||||
return (
|
||||
<PostRegistration
|
||||
@@ -1343,38 +1443,42 @@ module.exports = React.createClass({
|
||||
);
|
||||
}
|
||||
|
||||
// `ready` and `loggedIn` may be set before `page_type` (because the
|
||||
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||
// keep showing the spinner for now.
|
||||
if (this.state.loggedIn && this.state.ready && this.state.page_type) {
|
||||
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
||||
* we should go through and figure out what we actually need to pass down, as well
|
||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||
*/
|
||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||
return (
|
||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
||||
onRoomCreated={this.onRoomCreated}
|
||||
onUserSettingsClose={this.onUserSettingsClose}
|
||||
onRegistered={this.onRegistered}
|
||||
currentRoomId={this.state.currentRoomId}
|
||||
teamToken={this._teamToken}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.loggedIn) {
|
||||
// we think we are logged in, but are still waiting for the /sync to complete
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
<Spinner />
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||
{ _t('Logout') }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.screen == 'register') {
|
||||
if (this.state.view === VIEWS.LOGGED_IN) {
|
||||
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
|
||||
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||
// keep showing the spinner for now.
|
||||
if (this.state.ready && this.state.page_type) {
|
||||
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
||||
* we should go through and figure out what we actually need to pass down, as well
|
||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||
*/
|
||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||
return (
|
||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
||||
onRoomCreated={this.onRoomCreated}
|
||||
onUserSettingsClose={this.onUserSettingsClose}
|
||||
onRegistered={this.onRegistered}
|
||||
currentRoomId={this.state.currentRoomId}
|
||||
teamToken={this._teamToken}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// we think we are logged in, but are still waiting for the /sync to complete
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
<Spinner />
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||
{ _t('Logout') }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.REGISTER) {
|
||||
const Registration = sdk.getComponent('structures.login.Registration');
|
||||
return (
|
||||
<Registration
|
||||
@@ -1394,10 +1498,13 @@ module.exports = React.createClass({
|
||||
onLoggedIn={this.onRegistered}
|
||||
onLoginClick={this.onLoginClick}
|
||||
onRegisterClick={this.onRegisterClick}
|
||||
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
|
||||
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.screen == 'forgot_password') {
|
||||
}
|
||||
|
||||
|
||||
if (this.state.view === VIEWS.FORGOT_PASSWORD) {
|
||||
const ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
||||
return (
|
||||
<ForgotPassword
|
||||
@@ -1409,7 +1516,9 @@ module.exports = React.createClass({
|
||||
onRegisterClick={this.onRegisterClick}
|
||||
onLoginClick={this.onLoginClick} />
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.LOGIN) {
|
||||
const Login = sdk.getComponent('structures.login.Login');
|
||||
return (
|
||||
<Login
|
||||
@@ -1423,9 +1532,11 @@ module.exports = React.createClass({
|
||||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||
enableGuest={this.props.enableGuest}
|
||||
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
|
||||
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
console.error(`Unknown view ${this.state.view}`);
|
||||
},
|
||||
});
|
||||
|
||||
141
src/components/structures/MyGroups.js
Normal file
141
src/components/structures/MyGroups.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 sdk from '../../index';
|
||||
import { _t, _tJsx } from '../../languageHandler';
|
||||
import withMatrixClient from '../../wrappers/withMatrixClient';
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import dis from '../../dispatcher';
|
||||
import PropTypes from 'prop-types';
|
||||
import Modal from '../../Modal';
|
||||
|
||||
const GroupTile = React.createClass({
|
||||
displayName: 'GroupTile',
|
||||
|
||||
propTypes: {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
e.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'view_group',
|
||||
group_id: this.props.groupId,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <a onClick={this.onClick} href="#">{this.props.groupId}</a>;
|
||||
},
|
||||
});
|
||||
|
||||
export default withMatrixClient(React.createClass({
|
||||
displayName: 'MyGroups',
|
||||
|
||||
propTypes: {
|
||||
matrixClient: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
groups: null,
|
||||
error: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._fetch();
|
||||
},
|
||||
|
||||
_onCreateGroupClick: function() {
|
||||
const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
||||
Modal.createDialog(CreateGroupDialog);
|
||||
},
|
||||
|
||||
_fetch: function() {
|
||||
this.props.matrixClient.getJoinedGroups().done((result) => {
|
||||
this.setState({groups: result.groups, error: null});
|
||||
}, (err) => {
|
||||
this.setState({groups: null, error: err});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
let content;
|
||||
if (this.state.groups) {
|
||||
const groupNodes = [];
|
||||
this.state.groups.forEach((g) => {
|
||||
groupNodes.push(
|
||||
<div key={g}>
|
||||
<GroupTile groupId={g} />
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
content = <div>
|
||||
<div>{_t('You are a member of these groups:')}</div>
|
||||
{groupNodes}
|
||||
</div>;
|
||||
} else if (this.state.error) {
|
||||
content = <div className="mx_MyGroups_error">
|
||||
{_t('Error whilst fetching joined groups')}
|
||||
</div>;
|
||||
} else {
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return <div className="mx_MyGroups">
|
||||
<SimpleRoomHeader title={ _t("Groups") } />
|
||||
<div className='mx_MyGroups_joinCreateBox'>
|
||||
<div className="mx_MyGroups_createBox">
|
||||
<div className="mx_MyGroups_joinCreateHeader">
|
||||
{_t('Create a new group')}
|
||||
</div>
|
||||
<AccessibleButton className='mx_MyGroups_joinCreateButton' onClick={this._onCreateGroupClick}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
|
||||
</AccessibleButton>
|
||||
{_t(
|
||||
'Create a group to represent your community! '+
|
||||
'Define a set of rooms and your own custom homepage '+
|
||||
'to mark out your space in the Matrix universe.',
|
||||
)}
|
||||
</div>
|
||||
<div className="mx_MyGroups_joinBox">
|
||||
<div className="mx_MyGroups_joinCreateHeader">
|
||||
{_t('Join an existing group')}
|
||||
</div>
|
||||
<AccessibleButton className='mx_MyGroups_joinCreateButton' onClick={this._onJoinGroupClick}>
|
||||
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
|
||||
</AccessibleButton>
|
||||
{_tJsx(
|
||||
'To join an exisitng group you\'ll have to '+
|
||||
'know its group identifier; this will look '+
|
||||
'something like <i>+example:matrix.org</i>.',
|
||||
/<i>(.*)<\/i>/,
|
||||
(sub) => <i>{sub}</i>,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_MyGroups_content">
|
||||
{content}
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
}));
|
||||
@@ -33,9 +33,6 @@ module.exports = React.createClass({
|
||||
// the room this statusbar is representing.
|
||||
room: React.PropTypes.object.isRequired,
|
||||
|
||||
// a TabComplete object
|
||||
tabComplete: React.PropTypes.object.isRequired,
|
||||
|
||||
// the number of messages which have arrived since we've been scrolled up
|
||||
numUnreadMessages: React.PropTypes.number,
|
||||
|
||||
@@ -143,12 +140,9 @@ module.exports = React.createClass({
|
||||
(this.state.usersTyping.length > 0) ||
|
||||
this.props.numUnreadMessages ||
|
||||
!this.props.atEndOfLiveTimeline ||
|
||||
this.props.hasActiveCall ||
|
||||
this.props.tabComplete.isTabCompleting()
|
||||
this.props.hasActiveCall
|
||||
) {
|
||||
return STATUS_BAR_EXPANDED;
|
||||
} else if (this.props.tabCompleteEntries) {
|
||||
return STATUS_BAR_HIDDEN;
|
||||
} else if (this.props.unsentMessageError) {
|
||||
return STATUS_BAR_EXPANDED_LARGE;
|
||||
}
|
||||
@@ -237,8 +231,6 @@ module.exports = React.createClass({
|
||||
|
||||
// return suitable content for the main (text) part of the status bar.
|
||||
_getContent: function() {
|
||||
var TabCompleteBar = sdk.getComponent('rooms.TabCompleteBar');
|
||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
|
||||
// no conn bar trumps unread count since you can't get unread messages
|
||||
@@ -259,20 +251,6 @@ module.exports = React.createClass({
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.tabComplete.isTabCompleting()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_tabCompleteBar">
|
||||
<div className="mx_RoomStatusBar_tabCompleteWrapper">
|
||||
<TabCompleteBar tabComplete={this.props.tabComplete} />
|
||||
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
|
||||
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
||||
{_t('Auto-complete')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.unsentMessageError) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||
|
||||
@@ -22,7 +22,7 @@ limitations under the License.
|
||||
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var q = require("q");
|
||||
import Promise from 'bluebird';
|
||||
var classNames = require("classnames");
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
import { _t } from '../../languageHandler';
|
||||
@@ -33,7 +33,6 @@ var ContentMessages = require("../../ContentMessages");
|
||||
var Modal = require("../../Modal");
|
||||
var sdk = require('../../index');
|
||||
var CallHandler = require('../../CallHandler');
|
||||
var TabComplete = require("../../TabComplete");
|
||||
var Resend = require("../../Resend");
|
||||
var dis = require("../../dispatcher");
|
||||
var Tinter = require("../../Tinter");
|
||||
@@ -47,13 +46,14 @@ import UserProvider from '../../autocomplete/UserProvider';
|
||||
|
||||
import RoomViewStore from '../../stores/RoomViewStore';
|
||||
|
||||
var DEBUG = false;
|
||||
let DEBUG = false;
|
||||
let debuglog = function() {};
|
||||
|
||||
const BROWSER_SUPPORTS_SANDBOX = 'sandbox' in document.createElement('iframe');
|
||||
|
||||
if (DEBUG) {
|
||||
// using bind means that we get to keep useful line numbers in the console
|
||||
var debuglog = console.log.bind(console);
|
||||
} else {
|
||||
var debuglog = function() {};
|
||||
debuglog = console.log.bind(console);
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
@@ -93,6 +93,7 @@ module.exports = React.createClass({
|
||||
roomId: null,
|
||||
roomLoading: true,
|
||||
peekLoading: false,
|
||||
shouldPeek: true,
|
||||
|
||||
// The event to be scrolled to initially
|
||||
initialEventId: null,
|
||||
@@ -112,6 +113,7 @@ module.exports = React.createClass({
|
||||
callState: null,
|
||||
guestsCanJoin: false,
|
||||
canPeek: false,
|
||||
showApps: false,
|
||||
|
||||
// error object, as from the matrix client/server API
|
||||
// If we failed to load information about the room,
|
||||
@@ -141,15 +143,6 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership);
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
|
||||
this.tabComplete = new TabComplete({
|
||||
allowLooping: false,
|
||||
autoEnterTabComplete: true,
|
||||
onClickCompletes: true,
|
||||
onStateChange: (isCompleting) => {
|
||||
this.forceUpdate();
|
||||
},
|
||||
});
|
||||
|
||||
// Start listening for RoomViewStore updates
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._onRoomViewStoreUpdate(true);
|
||||
@@ -168,8 +161,14 @@ module.exports = React.createClass({
|
||||
initialEventId: RoomViewStore.getInitialEventId(),
|
||||
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
|
||||
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||
forwardingEvent: RoomViewStore.getForwardingEvent(),
|
||||
shouldPeek: RoomViewStore.shouldPeek(),
|
||||
};
|
||||
|
||||
// finished joining, start waiting for a room and show a spinner. See onRoom.
|
||||
newState.waitingForRoom = this.state.joining && !newState.joining &&
|
||||
!RoomViewStore.getJoinError();
|
||||
|
||||
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
|
||||
console.log(
|
||||
'RVS update:',
|
||||
@@ -177,12 +176,11 @@ module.exports = React.createClass({
|
||||
newState.roomAlias,
|
||||
'loading?', newState.roomLoading,
|
||||
'joining?', newState.joining,
|
||||
'initial?', initial,
|
||||
'waiting?', newState.waitingForRoom,
|
||||
'shouldPeek?', newState.shouldPeek,
|
||||
);
|
||||
|
||||
// finished joining, start waiting for a room and show a spinner. See onRoom.
|
||||
newState.waitingForRoom = this.state.joining && !newState.joining &&
|
||||
!RoomViewStore.getJoinError();
|
||||
|
||||
// NB: This does assume that the roomID will not change for the lifetime of
|
||||
// the RoomView instance
|
||||
if (initial) {
|
||||
@@ -228,13 +226,16 @@ module.exports = React.createClass({
|
||||
// making it impossible to indicate a newly joined room.
|
||||
const room = this.state.room;
|
||||
if (room) {
|
||||
this._updateAutoComplete(room);
|
||||
this.tabComplete.loadEntries(room);
|
||||
this.setState({
|
||||
unsentMessageError: this._getUnsentMessageError(room),
|
||||
showApps: this._shouldShowApps(room),
|
||||
});
|
||||
this._onRoomLoaded(room);
|
||||
}
|
||||
if (!this.state.joining && this.state.roomId) {
|
||||
if (this.props.autoJoin) {
|
||||
this.onJoinButtonClicked();
|
||||
} else if (!room) {
|
||||
} else if (!room && this.state.shouldPeek) {
|
||||
console.log("Attempting to peek into room %s", this.state.roomId);
|
||||
this.setState({
|
||||
peekLoading: true,
|
||||
@@ -262,13 +263,22 @@ module.exports = React.createClass({
|
||||
} else if (room) {
|
||||
// Stop peeking because we have joined this room previously
|
||||
MatrixClientPeg.get().stopPeeking();
|
||||
this.setState({
|
||||
unsentMessageError: this._getUnsentMessageError(room),
|
||||
});
|
||||
this._onRoomLoaded(room);
|
||||
}
|
||||
},
|
||||
|
||||
_shouldShowApps: function(room) {
|
||||
if (!BROWSER_SUPPORTS_SANDBOX) return false;
|
||||
|
||||
const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
// any valid widget = show apps
|
||||
for (let i = 0; i < appsStateEvents.length; i++) {
|
||||
if (appsStateEvents[i].getContent().type && appsStateEvents[i].getContent().url) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var call = this._getCallForRoom();
|
||||
var callState = call ? call.call_state : "ended";
|
||||
@@ -449,13 +459,13 @@ module.exports = React.createClass({
|
||||
this._updateConfCallNotification();
|
||||
|
||||
this.setState({
|
||||
callState: callState
|
||||
callState: callState,
|
||||
});
|
||||
|
||||
break;
|
||||
case 'forward_event':
|
||||
case 'appsDrawer':
|
||||
this.setState({
|
||||
forwardingEvent: payload.content,
|
||||
showApps: payload.show,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -498,9 +508,7 @@ module.exports = React.createClass({
|
||||
// update the tab complete list as it depends on who most recently spoke,
|
||||
// and that has probably just changed
|
||||
if (ev.sender) {
|
||||
this.tabComplete.onMemberSpoke(ev.sender);
|
||||
// nb. we don't need to update the new autocomplete here since
|
||||
// its results are currently ordered purely by search score.
|
||||
UserProvider.getInstance().onUserSpoke(ev.sender);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -523,6 +531,7 @@ module.exports = React.createClass({
|
||||
this._warnAboutEncryption(room);
|
||||
this._calculatePeekRules(room);
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
UserProvider.getInstance().setUserListFromRoom(room);
|
||||
},
|
||||
|
||||
_warnAboutEncryption: function(room) {
|
||||
@@ -698,8 +707,7 @@ module.exports = React.createClass({
|
||||
this._updateConfCallNotification();
|
||||
|
||||
// refresh the tab complete list
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
this._updateAutoComplete(this.state.room);
|
||||
UserProvider.getInstance().setUserListFromRoom(this.state.room);
|
||||
|
||||
// if we are now a member of the room, where we were not before, that
|
||||
// means we have finished joining a room we were previously peeking
|
||||
@@ -767,7 +775,7 @@ module.exports = React.createClass({
|
||||
|
||||
onSearchResultsFillRequest: function(backwards) {
|
||||
if (!backwards) {
|
||||
return q(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (this.state.searchResults.next_batch) {
|
||||
@@ -777,7 +785,7 @@ module.exports = React.createClass({
|
||||
return this._handleSearchResult(searchPromise);
|
||||
} else {
|
||||
debuglog("no more search results");
|
||||
return q(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -838,7 +846,7 @@ module.exports = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
q().then(() => {
|
||||
Promise.resolve().then(() => {
|
||||
const signUrl = this.props.thirdPartyInvite ?
|
||||
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||
dis.dispatch({
|
||||
@@ -857,7 +865,7 @@ module.exports = React.createClass({
|
||||
}
|
||||
}
|
||||
}
|
||||
return q();
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1164,8 +1172,13 @@ module.exports = React.createClass({
|
||||
this.updateTint();
|
||||
this.setState({
|
||||
editingRoomSettings: false,
|
||||
forwardingEvent: null,
|
||||
});
|
||||
if (this.state.forwardingEvent) {
|
||||
dis.dispatch({
|
||||
action: 'forward_event',
|
||||
event: null,
|
||||
});
|
||||
}
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
},
|
||||
|
||||
@@ -1419,14 +1432,6 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_updateAutoComplete: function(room) {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const members = room.getJoinedMembers().filter(function(member) {
|
||||
if (member.userId !== myUserId) return true;
|
||||
});
|
||||
UserProvider.getInstance().setUserList(members);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
@@ -1463,7 +1468,7 @@ module.exports = React.createClass({
|
||||
|
||||
// We have no room object for this room, only the ID.
|
||||
// We've got to this room by following a link, possibly a third party invite.
|
||||
var room_alias = this.state.room_alias;
|
||||
const roomAlias = this.state.roomAlias;
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<RoomHeader ref="header"
|
||||
@@ -1476,7 +1481,7 @@ module.exports = React.createClass({
|
||||
onForgetClick={ this.onForgetClick }
|
||||
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
||||
canPreview={ false } error={ this.state.roomLoadError }
|
||||
roomAlias={room_alias}
|
||||
roomAlias={roomAlias}
|
||||
spinner={previewBarSpinner}
|
||||
inviterName={inviterName}
|
||||
invitedEmail={invitedEmail}
|
||||
@@ -1554,7 +1559,6 @@ module.exports = React.createClass({
|
||||
isStatusAreaExpanded = this.state.statusBarVisible;
|
||||
statusBar = <RoomStatusBar
|
||||
room={this.state.room}
|
||||
tabComplete={this.tabComplete}
|
||||
numUnreadMessages={this.state.numUnreadMessages}
|
||||
unsentMessageError={this.state.unsentMessageError}
|
||||
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
|
||||
@@ -1576,7 +1580,7 @@ module.exports = React.createClass({
|
||||
} else if (this.state.uploadingRoomSettings) {
|
||||
aux = <Loader/>;
|
||||
} else if (this.state.forwardingEvent !== null) {
|
||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} currentRoomId={this.state.room.roomId} mxEvent={this.state.forwardingEvent} />;
|
||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
|
||||
} else if (this.state.searching) {
|
||||
hideCancel = true; // has own cancel
|
||||
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch}/>;
|
||||
@@ -1607,11 +1611,13 @@ module.exports = React.createClass({
|
||||
|
||||
var auxPanel = (
|
||||
<AuxPanel ref="auxPanel" room={this.state.room}
|
||||
userId={MatrixClientPeg.get().credentials.userId}
|
||||
conferenceHandler={this.props.ConferenceHandler}
|
||||
draggingFile={this.state.draggingFile}
|
||||
displayConfCallNotification={this.state.displayConfCallNotification}
|
||||
maxHeight={this.state.auxPanelMaxHeight}
|
||||
onResize={this.onChildResize} >
|
||||
onResize={this.onChildResize}
|
||||
showApps={this.state.showApps && !this.state.editingRoomSettings} >
|
||||
{ aux }
|
||||
</AuxPanel>
|
||||
);
|
||||
@@ -1624,8 +1630,13 @@ module.exports = React.createClass({
|
||||
if (canSpeak) {
|
||||
messageComposer =
|
||||
<MessageComposer
|
||||
room={this.state.room} onResize={this.onChildResize} uploadFile={this.uploadFile}
|
||||
callState={this.state.callState} tabComplete={this.tabComplete} opacity={ this.props.opacity }/>;
|
||||
room={this.state.room}
|
||||
onResize={this.onChildResize}
|
||||
uploadFile={this.uploadFile}
|
||||
callState={this.state.callState}
|
||||
opacity={ this.props.opacity }
|
||||
showApps={ this.state.showApps }
|
||||
/>;
|
||||
}
|
||||
|
||||
// TODO: Why aren't we storing the term/scope/count in this format
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
var q = require("q");
|
||||
import Promise from 'bluebird';
|
||||
var KeyCode = require('../../KeyCode');
|
||||
|
||||
var DEBUG_SCROLL = false;
|
||||
@@ -145,7 +145,7 @@ module.exports = React.createClass({
|
||||
return {
|
||||
stickyBottom: true,
|
||||
startAtBottom: true,
|
||||
onFillRequest: function(backwards) { return q(false); },
|
||||
onFillRequest: function(backwards) { return Promise.resolve(false); },
|
||||
onUnfillRequest: function(backwards, scrollToken) {},
|
||||
onScroll: function() {},
|
||||
};
|
||||
@@ -386,19 +386,12 @@ module.exports = React.createClass({
|
||||
debuglog("ScrollPanel: starting "+dir+" fill");
|
||||
|
||||
// onFillRequest can end up calling us recursively (via onScroll
|
||||
// events) so make sure we set this before firing off the call. That
|
||||
// does present the risk that we might not ever actually fire off the
|
||||
// fill request, so wrap it in a try/catch.
|
||||
// events) so make sure we set this before firing off the call.
|
||||
this._pendingFillRequests[dir] = true;
|
||||
var fillPromise;
|
||||
try {
|
||||
fillPromise = this.props.onFillRequest(backwards);
|
||||
} catch (e) {
|
||||
this._pendingFillRequests[dir] = false;
|
||||
throw e;
|
||||
}
|
||||
|
||||
q.finally(fillPromise, () => {
|
||||
Promise.try(() => {
|
||||
return this.props.onFillRequest(backwards);
|
||||
}).finally(() => {
|
||||
this._pendingFillRequests[dir] = false;
|
||||
}).then((hasMoreResults) => {
|
||||
if (this.unmounted) {
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require("react-dom");
|
||||
var q = require("q");
|
||||
import Promise from 'bluebird';
|
||||
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var EventTimeline = Matrix.EventTimeline;
|
||||
@@ -314,13 +314,13 @@ var TimelinePanel = React.createClass({
|
||||
|
||||
if (!this.state[canPaginateKey]) {
|
||||
debuglog("TimelinePanel: have given up", dir, "paginating this timeline");
|
||||
return q(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if(!this._timelineWindow.canPaginate(dir)) {
|
||||
debuglog("TimelinePanel: can't", dir, "paginate any further");
|
||||
this.setState({[canPaginateKey]: false});
|
||||
return q(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
debuglog("TimelinePanel: Initiating paginate; backwards:"+backwards);
|
||||
@@ -353,9 +353,9 @@ var TimelinePanel = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
onMessageListScroll: function() {
|
||||
onMessageListScroll: function(e) {
|
||||
if (this.props.onScroll) {
|
||||
this.props.onScroll();
|
||||
this.props.onScroll(e);
|
||||
}
|
||||
|
||||
if (this.props.manageReadMarkers) {
|
||||
|
||||
@@ -21,7 +21,8 @@ const MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
const PlatformPeg = require("../../PlatformPeg");
|
||||
const Modal = require('../../Modal');
|
||||
const dis = require("../../dispatcher");
|
||||
const q = require('q');
|
||||
import sessionStore from '../../stores/SessionStore';
|
||||
import Promise from 'bluebird';
|
||||
const packageJson = require('../../../package.json');
|
||||
const UserSettingsStore = require('../../UserSettingsStore');
|
||||
const CallMediaHandler = require('../../CallMediaHandler');
|
||||
@@ -93,8 +94,12 @@ const SETTINGS_LABELS = [
|
||||
label: 'Hide removed messages',
|
||||
},
|
||||
{
|
||||
id: 'disableMarkdown',
|
||||
label: 'Disable markdown formatting',
|
||||
id: 'enableSyntaxHighlightLanguageDetection',
|
||||
label: 'Enable automatic language detection for syntax highlighting',
|
||||
},
|
||||
{
|
||||
id: 'MessageComposerInput.autoReplaceEmoji',
|
||||
label: 'Automatically replace plain text Emoji',
|
||||
},
|
||||
/*
|
||||
{
|
||||
@@ -173,9 +178,6 @@ module.exports = React.createClass({
|
||||
// The base URL to use in the referral link. Defaults to window.location.origin.
|
||||
referralBaseUrl: React.PropTypes.string,
|
||||
|
||||
// true if RightPanel is collapsed
|
||||
collapsedRhs: React.PropTypes.bool,
|
||||
|
||||
// Team token for the referral link. If falsy, the referral section will
|
||||
// not appear
|
||||
teamToken: React.PropTypes.string,
|
||||
@@ -205,7 +207,7 @@ module.exports = React.createClass({
|
||||
this._addThreepid = null;
|
||||
|
||||
if (PlatformPeg.get()) {
|
||||
q().then(() => {
|
||||
Promise.resolve().then(() => {
|
||||
return PlatformPeg.get().getAppVersion();
|
||||
}).done((appVersion) => {
|
||||
if (this._unmounted) return;
|
||||
@@ -250,6 +252,12 @@ module.exports = React.createClass({
|
||||
this.setState({
|
||||
language: languageHandler.getCurrentLanguage(),
|
||||
});
|
||||
|
||||
this._sessionStore = sessionStore;
|
||||
this._sessionStoreToken = this._sessionStore.addListener(
|
||||
this._setStateFromSessionStore,
|
||||
);
|
||||
this._setStateFromSessionStore();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@@ -276,12 +284,28 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
// `UserSettings` assumes that the client peg will not be null, so give it some
|
||||
// sort of assurance here by only allowing a re-render if the client is truthy.
|
||||
//
|
||||
// This is required because `UserSettings` maintains its own state and if this state
|
||||
// updates (e.g. during _setStateFromSessionStore) after the client peg has been made
|
||||
// null (during logout), then it will attempt to re-render and throw errors.
|
||||
shouldComponentUpdate: function() {
|
||||
return Boolean(MatrixClientPeg.get());
|
||||
},
|
||||
|
||||
_setStateFromSessionStore: function() {
|
||||
this.setState({
|
||||
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
|
||||
});
|
||||
},
|
||||
|
||||
_electronSettings: function(ev, settings) {
|
||||
this.setState({ electron_settings: settings });
|
||||
},
|
||||
|
||||
_refreshMediaDevices: function() {
|
||||
q().then(() => {
|
||||
Promise.resolve().then(() => {
|
||||
return CallMediaHandler.getDevices();
|
||||
}).then((mediaDevices) => {
|
||||
// console.log("got mediaDevices", mediaDevices, this._unmounted);
|
||||
@@ -296,7 +320,7 @@ module.exports = React.createClass({
|
||||
|
||||
_refreshFromServer: function() {
|
||||
const self = this;
|
||||
q.all([
|
||||
Promise.all([
|
||||
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids(),
|
||||
]).done(function(resps) {
|
||||
self.setState({
|
||||
@@ -548,15 +572,16 @@ module.exports = React.createClass({
|
||||
});
|
||||
// reject the invites
|
||||
const promises = rooms.map((room) => {
|
||||
return MatrixClientPeg.get().leave(room.roomId);
|
||||
return MatrixClientPeg.get().leave(room.roomId).catch((e) => {
|
||||
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
|
||||
// after trying to reject all the invites.
|
||||
});
|
||||
});
|
||||
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
|
||||
// after trying to reject all the invites.
|
||||
q.allSettled(promises).then(() => {
|
||||
Promise.all(promises).then(() => {
|
||||
this.setState({
|
||||
rejectingInvites: false,
|
||||
});
|
||||
}).done();
|
||||
});
|
||||
},
|
||||
|
||||
_onExportE2eKeysClicked: function() {
|
||||
@@ -626,6 +651,10 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
_renderUserInterfaceSettings: function() {
|
||||
// TODO: this ought to be a separate component so that we don't need
|
||||
// to rebind the onChange each time we render
|
||||
const onChange = (e) =>
|
||||
UserSettingsStore.setLocalSetting('autocompleteDelay', + e.target.value);
|
||||
return (
|
||||
<div>
|
||||
<h3>{ _t("User Interface") }</h3>
|
||||
@@ -633,8 +662,21 @@ module.exports = React.createClass({
|
||||
{ this._renderUrlPreviewSelector() }
|
||||
{ SETTINGS_LABELS.map( this._renderSyncedSetting ) }
|
||||
{ THEMES.map( this._renderThemeSelector ) }
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{_t('Autocomplete Delay (ms):')}</strong></td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
defaultValue={UserSettingsStore.getLocalSetting('autocompleteDelay', 200)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{ this._renderLanguageSetting() }
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -868,6 +910,21 @@ module.exports = React.createClass({
|
||||
</div>;
|
||||
},
|
||||
|
||||
_renderCheckUpdate: function() {
|
||||
const platform = PlatformPeg.get();
|
||||
if ('canSelfUpdate' in platform && platform.canSelfUpdate() && 'startUpdateCheck' in platform) {
|
||||
return <div>
|
||||
<h3>{_t('Updates')}</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<AccessibleButton className="mx_UserSettings_button" onClick={platform.startUpdateCheck}>
|
||||
{_t('Check for update')}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
return <div />;
|
||||
},
|
||||
|
||||
_renderBulkOptions: function() {
|
||||
const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
||||
return r.hasMembershipState(this._me, "invite");
|
||||
@@ -1168,7 +1225,6 @@ module.exports = React.createClass({
|
||||
<div className="mx_UserSettings">
|
||||
<SimpleRoomHeader
|
||||
title={ _t("Settings") }
|
||||
collapsedRhs={ this.props.collapsedRhs }
|
||||
onCancelClick={ this.props.onClose }
|
||||
/>
|
||||
|
||||
@@ -1209,10 +1265,14 @@ module.exports = React.createClass({
|
||||
<h3>{ _t("Account") }</h3>
|
||||
|
||||
<div className="mx_UserSettings_section cadcampoHide">
|
||||
|
||||
<AccessibleButton className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}>
|
||||
{ _t("Sign out") }
|
||||
</AccessibleButton>
|
||||
{ this.state.userHasGeneratedPassword ?
|
||||
<div className="mx_UserSettings_passwordWarning">
|
||||
{ _t("To return to your account in future you need to set a password") }
|
||||
</div> : null
|
||||
}
|
||||
|
||||
{accountJsx}
|
||||
</div>
|
||||
@@ -1266,6 +1326,8 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this._renderCheckUpdate()}
|
||||
|
||||
{this._renderClearCache()}
|
||||
|
||||
{this._renderDeactivateAccount()}
|
||||
|
||||
@@ -72,9 +72,14 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
this._initLoginLogic();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._unmounted = true;
|
||||
},
|
||||
|
||||
onPasswordLogin: function(username, phoneCountry, phoneNumber, password) {
|
||||
this.setState({
|
||||
busy: true,
|
||||
@@ -87,6 +92,9 @@ module.exports = React.createClass({
|
||||
).then((data) => {
|
||||
this.props.onLoggedIn(data);
|
||||
}, (error) => {
|
||||
if(this._unmounted) {
|
||||
return;
|
||||
}
|
||||
let errorText;
|
||||
|
||||
// Some error strings only apply for logging in
|
||||
@@ -109,8 +117,11 @@ module.exports = React.createClass({
|
||||
loginIncorrect: error.httpStatus === 401 || error.httpStatus == 403,
|
||||
});
|
||||
}).finally(() => {
|
||||
if(this._unmounted) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
busy: false
|
||||
busy: false,
|
||||
});
|
||||
}).done();
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
|
||||
import q from 'q';
|
||||
import Promise from 'bluebird';
|
||||
import React from 'react';
|
||||
|
||||
import sdk from '../../../index';
|
||||
@@ -180,7 +180,7 @@ module.exports = React.createClass({
|
||||
// will just nop. The point of this being we might not have the email address
|
||||
// that the user registered with at this stage (depending on whether this
|
||||
// is the client they initiated registration).
|
||||
let trackPromise = q(null);
|
||||
let trackPromise = Promise.resolve(null);
|
||||
if (this._rtsClient && extra.emailSid) {
|
||||
// Track referral if this.props.referrer set, get team_token in order to
|
||||
// retrieve team config and see welcome page etc.
|
||||
@@ -218,29 +218,29 @@ module.exports = React.createClass({
|
||||
}
|
||||
|
||||
trackPromise.then((teamToken) => {
|
||||
this.props.onLoggedIn({
|
||||
return this.props.onLoggedIn({
|
||||
userId: response.user_id,
|
||||
deviceId: response.device_id,
|
||||
homeserverUrl: this._matrixClient.getHomeserverUrl(),
|
||||
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
|
||||
accessToken: response.access_token
|
||||
}, teamToken);
|
||||
}).then(() => {
|
||||
return this._setupPushers();
|
||||
}).then((cli) => {
|
||||
return this._setupPushers(cli);
|
||||
});
|
||||
},
|
||||
|
||||
_setupPushers: function() {
|
||||
_setupPushers: function(matrixClient) {
|
||||
if (!this.props.brand) {
|
||||
return q();
|
||||
return Promise.resolve();
|
||||
}
|
||||
return MatrixClientPeg.get().getPushers().then((resp)=>{
|
||||
return matrixClient.getPushers().then((resp)=>{
|
||||
const pushers = resp.pushers;
|
||||
for (let i = 0; i < pushers.length; ++i) {
|
||||
if (pushers[i].kind == 'email') {
|
||||
const emailPusher = pushers[i];
|
||||
emailPusher.data = { brand: this.props.brand };
|
||||
MatrixClientPeg.get().setPusher(emailPusher).done(() => {
|
||||
matrixClient.setPusher(emailPusher).done(() => {
|
||||
console.log("Set email branding to " + this.props.brand);
|
||||
}, (error) => {
|
||||
console.error("Couldn't set email branding: " + error);
|
||||
|
||||
Reference in New Issue
Block a user