mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-01 11:03:18 +03:00
Merge remote-tracking branch 'origin/develop' into dbkr/threepid_display
This commit is contained in:
commit
92d5f55a46
14 changed files with 225 additions and 164 deletions
|
@ -18,13 +18,17 @@ import dis from './dispatcher';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
let isDialogOpen = false;
|
||||||
|
|
||||||
const onAction = function(payload) {
|
const onAction = function(payload) {
|
||||||
if (payload.action === 'unknown_device_error') {
|
if (payload.action === 'unknown_device_error' && !isDialogOpen) {
|
||||||
var UnknownDeviceDialog = sdk.getComponent("dialogs.UnknownDeviceDialog");
|
var UnknownDeviceDialog = sdk.getComponent("dialogs.UnknownDeviceDialog");
|
||||||
|
isDialogOpen = true;
|
||||||
Modal.createDialog(UnknownDeviceDialog, {
|
Modal.createDialog(UnknownDeviceDialog, {
|
||||||
devices: payload.err.devices,
|
devices: payload.err.devices,
|
||||||
room: payload.room,
|
room: payload.room,
|
||||||
onFinished: (r) => {
|
onFinished: (r) => {
|
||||||
|
isDialogOpen = false;
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
// https://github.com/vector-im/riot-web/issues/3148
|
||||||
console.log('UnknownDeviceDialog closed with '+r);
|
console.log('UnknownDeviceDialog closed with '+r);
|
||||||
|
|
|
@ -63,6 +63,13 @@ module.exports = React.createClass({
|
||||||
// called when the session load completes
|
// called when the session load completes
|
||||||
onLoadCompleted: React.PropTypes.func,
|
onLoadCompleted: React.PropTypes.func,
|
||||||
|
|
||||||
|
// Represents the screen to display as a result of parsing the initial
|
||||||
|
// window.location
|
||||||
|
initialScreenAfterLogin: React.PropTypes.shape({
|
||||||
|
screen: React.PropTypes.string.isRequired,
|
||||||
|
params: React.PropTypes.object,
|
||||||
|
}),
|
||||||
|
|
||||||
// displayname, if any, to set on the device when logging
|
// displayname, if any, to set on the device when logging
|
||||||
// in/registering.
|
// in/registering.
|
||||||
defaultDeviceDisplayName: React.PropTypes.string,
|
defaultDeviceDisplayName: React.PropTypes.string,
|
||||||
|
@ -89,6 +96,12 @@ module.exports = React.createClass({
|
||||||
var s = {
|
var s = {
|
||||||
loading: true,
|
loading: true,
|
||||||
screen: undefined,
|
screen: undefined,
|
||||||
|
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
|
||||||
// What the LoggedInView would be showing if visible
|
// What the LoggedInView would be showing if visible
|
||||||
page_type: null,
|
page_type: null,
|
||||||
|
@ -184,11 +197,6 @@ module.exports = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
SdkConfig.put(this.props.config);
|
SdkConfig.put(this.props.config);
|
||||||
|
|
||||||
// Stashed guest credentials if the user logs out
|
|
||||||
// whilst logged in as a guest user (so they can change
|
|
||||||
// their mind & log back in)
|
|
||||||
this.guestCreds = null;
|
|
||||||
|
|
||||||
// if the automatic session load failed, the error
|
// if the automatic session load failed, the error
|
||||||
this.sessionLoadError = null;
|
this.sessionLoadError = null;
|
||||||
|
|
||||||
|
@ -317,14 +325,13 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
var roomIndexDelta = 1;
|
var roomIndexDelta = 1;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'logout':
|
case 'logout':
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
this.guestCreds = MatrixClientPeg.getCredentials();
|
|
||||||
}
|
|
||||||
Lifecycle.logout();
|
Lifecycle.logout();
|
||||||
break;
|
break;
|
||||||
case 'start_registration':
|
case 'start_registration':
|
||||||
|
@ -344,7 +351,13 @@ module.exports = React.createClass({
|
||||||
this.notifyNewScreen('register');
|
this.notifyNewScreen('register');
|
||||||
break;
|
break;
|
||||||
case 'start_login':
|
case 'start_login':
|
||||||
if (this.state.logged_in) return;
|
if (MatrixClientPeg.get() &&
|
||||||
|
MatrixClientPeg.get().isGuest()
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
guestCreds: MatrixClientPeg.getCredentials(),
|
||||||
|
});
|
||||||
|
}
|
||||||
this.setStateForNewScreen({
|
this.setStateForNewScreen({
|
||||||
screen: 'login',
|
screen: 'login',
|
||||||
});
|
});
|
||||||
|
@ -359,8 +372,8 @@ module.exports = React.createClass({
|
||||||
// also stash our credentials, then if we restore the session,
|
// also stash our credentials, then if we restore the session,
|
||||||
// we can just do it the same way whether we started upgrade
|
// we can just do it the same way whether we started upgrade
|
||||||
// registration or explicitly logged out
|
// registration or explicitly logged out
|
||||||
this.guestCreds = MatrixClientPeg.getCredentials();
|
|
||||||
this.setStateForNewScreen({
|
this.setStateForNewScreen({
|
||||||
|
guestCreds: MatrixClientPeg.getCredentials(),
|
||||||
screen: "register",
|
screen: "register",
|
||||||
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
|
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
|
||||||
guestAccessToken: MatrixClientPeg.get().getAccessToken(),
|
guestAccessToken: MatrixClientPeg.get().getAccessToken(),
|
||||||
|
@ -382,25 +395,23 @@ module.exports = React.createClass({
|
||||||
this.notifyNewScreen('forgot_password');
|
this.notifyNewScreen('forgot_password');
|
||||||
break;
|
break;
|
||||||
case 'leave_room':
|
case 'leave_room':
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
|
|
||||||
var roomId = payload.room_id;
|
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Leave room",
|
title: "Leave room",
|
||||||
description: "Are you sure you want to leave the room?",
|
description: "Are you sure you want to leave the room?",
|
||||||
onFinished: function(should_leave) {
|
onFinished: (should_leave) => {
|
||||||
if (should_leave) {
|
if (should_leave) {
|
||||||
var d = MatrixClientPeg.get().leave(roomId);
|
const d = MatrixClientPeg.get().leave(payload.room_id);
|
||||||
|
|
||||||
// FIXME: controller shouldn't be loading a view :(
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
var modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
d.then(function() {
|
d.then(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
dis.dispatch({action: 'view_next_room'});
|
if (this.currentRoomId === payload.room_id) {
|
||||||
}, function(err) {
|
dis.dispatch({action: 'view_next_room'});
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
modal.close();
|
modal.close();
|
||||||
console.error("Failed to leave room " + payload.room_id + " " + err);
|
console.error("Failed to leave room " + payload.room_id + " " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
@ -412,6 +423,32 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'reject_invite':
|
||||||
|
Modal.createDialog(QuestionDialog, {
|
||||||
|
title: "Reject invitation",
|
||||||
|
description: "Are you sure you want to reject the invitation?",
|
||||||
|
onFinished: (confirm) => {
|
||||||
|
if (confirm) {
|
||||||
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
|
MatrixClientPeg.get().leave(payload.room_id).done(() => {
|
||||||
|
modal.close();
|
||||||
|
if (this.currentRoomId === payload.room_id) {
|
||||||
|
dis.dispatch({action: 'view_next_room'});
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
modal.close();
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Failed to reject invitation",
|
||||||
|
description: err.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
case 'view_user':
|
case 'view_user':
|
||||||
// FIXME: ugly hack to expand the RightPanel and then re-dispatch.
|
// FIXME: ugly hack to expand the RightPanel and then re-dispatch.
|
||||||
if (this.state.collapse_rhs) {
|
if (this.state.collapse_rhs) {
|
||||||
|
@ -659,6 +696,14 @@ module.exports = React.createClass({
|
||||||
_onLoadCompleted: function() {
|
_onLoadCompleted: function() {
|
||||||
this.props.onLoadCompleted();
|
this.props.onLoadCompleted();
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
|
|
||||||
|
// Show screens (like 'register') that need to be shown without onLoggedIn
|
||||||
|
// being called. 'register' needs to be routed here when the email confirmation
|
||||||
|
// link is clicked on.
|
||||||
|
if (this.state.screenAfterLogin &&
|
||||||
|
['register'].indexOf(this.state.screenAfterLogin.screen) !== -1) {
|
||||||
|
this._showScreenAfterLogin();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -709,18 +754,33 @@ module.exports = React.createClass({
|
||||||
* Called when a new logged in session has started
|
* Called when a new logged in session has started
|
||||||
*/
|
*/
|
||||||
_onLoggedIn: function(teamToken) {
|
_onLoggedIn: function(teamToken) {
|
||||||
this.guestCreds = null;
|
|
||||||
this.notifyNewScreen('');
|
|
||||||
this.setState({
|
this.setState({
|
||||||
screen: undefined,
|
guestCreds: null,
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (teamToken) {
|
if (teamToken) {
|
||||||
this._teamToken = teamToken;
|
this._teamToken = teamToken;
|
||||||
this._setPage(PageTypes.HomePage);
|
dis.dispatch({action: 'view_home_page'});
|
||||||
} else if (this._is_registered) {
|
} else if (this._is_registered) {
|
||||||
this._setPage(PageTypes.UserSettings);
|
dis.dispatch({action: 'view_user_settings'});
|
||||||
|
} else {
|
||||||
|
this._showScreenAfterLogin();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_showScreenAfterLogin: function() {
|
||||||
|
// If screenAfterLogin is set, use that, then null it so that a second login will
|
||||||
|
// result in view_home_page, _user_settings or _room_directory
|
||||||
|
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
|
||||||
|
this.showScreen(
|
||||||
|
this.state.screenAfterLogin.screen,
|
||||||
|
this.state.screenAfterLogin.params
|
||||||
|
);
|
||||||
|
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
||||||
|
this.setState({screenAfterLogin: null});
|
||||||
|
} else {
|
||||||
|
dis.dispatch({action: 'view_room_directory'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -769,12 +829,6 @@ module.exports = React.createClass({
|
||||||
cli.getRooms()
|
cli.getRooms()
|
||||||
)[0].roomId;
|
)[0].roomId;
|
||||||
self.setState({ready: true, currentRoomId: firstRoom, page_type: PageTypes.RoomView});
|
self.setState({ready: true, currentRoomId: firstRoom, page_type: PageTypes.RoomView});
|
||||||
} else {
|
|
||||||
if (self._teamToken) {
|
|
||||||
self.setState({ready: true, page_type: PageTypes.HomePage});
|
|
||||||
} else {
|
|
||||||
self.setState({ready: true, page_type: PageTypes.RoomDirectory});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.setState({ready: true, page_type: PageTypes.RoomView});
|
self.setState({ready: true, page_type: PageTypes.RoomView});
|
||||||
|
@ -791,16 +845,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (presentedId != undefined) {
|
if (presentedId != undefined) {
|
||||||
self.notifyNewScreen('room/'+presentedId);
|
self.notifyNewScreen('room/'+presentedId);
|
||||||
} else {
|
|
||||||
// There is no information on presentedId
|
|
||||||
// so point user to fallback like /directory
|
|
||||||
if (self._teamToken) {
|
|
||||||
self.notifyNewScreen('home');
|
|
||||||
} else {
|
|
||||||
self.notifyNewScreen('directory');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({action: 'focus_composer'});
|
dis.dispatch({action: 'focus_composer'});
|
||||||
} else {
|
} else {
|
||||||
self.setState({ready: true});
|
self.setState({ready: true});
|
||||||
|
@ -1003,9 +1048,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onReturnToGuestClick: function() {
|
onReturnToGuestClick: function() {
|
||||||
// reanimate our guest login
|
// reanimate our guest login
|
||||||
if (this.guestCreds) {
|
if (this.state.guestCreds) {
|
||||||
Lifecycle.setLoggedIn(this.guestCreds);
|
Lifecycle.setLoggedIn(this.state.guestCreds);
|
||||||
this.guestCreds = null;
|
this.setState({guestCreds: null});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1154,7 +1199,7 @@ module.exports = React.createClass({
|
||||||
onLoggedIn={this.onRegistered}
|
onLoggedIn={this.onRegistered}
|
||||||
onLoginClick={this.onLoginClick}
|
onLoginClick={this.onLoginClick}
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
|
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.screen == 'forgot_password') {
|
} else if (this.state.screen == 'forgot_password') {
|
||||||
|
@ -1181,7 +1226,7 @@ module.exports = React.createClass({
|
||||||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||||
enableGuest={this.props.enableGuest}
|
enableGuest={this.props.enableGuest}
|
||||||
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
|
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
|
||||||
initialErrorText={this.sessionLoadError}
|
initialErrorText={this.sessionLoadError}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -413,7 +413,7 @@ module.exports = React.createClass({
|
||||||
var continuation = false;
|
var continuation = false;
|
||||||
|
|
||||||
if (prevEvent !== null
|
if (prevEvent !== null
|
||||||
&& !prevEvent.isRedacted() && prevEvent.sender && mxEv.sender
|
&& prevEvent.sender && mxEv.sender
|
||||||
&& mxEv.sender.userId === prevEvent.sender.userId
|
&& mxEv.sender.userId === prevEvent.sender.userId
|
||||||
&& mxEv.getType() == prevEvent.getType()) {
|
&& mxEv.getType() == prevEvent.getType()) {
|
||||||
continuation = true;
|
continuation = true;
|
||||||
|
|
|
@ -915,8 +915,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadFile: function(file) {
|
uploadFile: function(file) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
|
@ -928,8 +926,16 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
ContentMessages.sendContentToRoom(
|
ContentMessages.sendContentToRoom(
|
||||||
file, this.state.room.roomId, MatrixClientPeg.get()
|
file, this.state.room.roomId, MatrixClientPeg.get()
|
||||||
).done(undefined, function(error) {
|
).done(undefined, (error) => {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
if (error.name === "UnknownDeviceError") {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'unknown_device_error',
|
||||||
|
err: error,
|
||||||
|
room: this.state.room,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Failed to upload file " + file + " " + error);
|
console.error("Failed to upload file " + file + " " + error);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to upload file",
|
title: "Failed to upload file",
|
||||||
|
|
|
@ -25,7 +25,7 @@ var DEBUG_SCROLL = false;
|
||||||
|
|
||||||
// The amount of extra scroll distance to allow prior to unfilling.
|
// The amount of extra scroll distance to allow prior to unfilling.
|
||||||
// See _getExcessHeight.
|
// See _getExcessHeight.
|
||||||
const UNPAGINATION_PADDING = 3000;
|
const UNPAGINATION_PADDING = 6000;
|
||||||
// The number of milliseconds to debounce calls to onUnfillRequest, to prevent
|
// The number of milliseconds to debounce calls to onUnfillRequest, to prevent
|
||||||
// many scroll events causing many unfilling requests.
|
// many scroll events causing many unfilling requests.
|
||||||
const UNFILL_REQUEST_DEBOUNCE_MS = 200;
|
const UNFILL_REQUEST_DEBOUNCE_MS = 200;
|
||||||
|
|
|
@ -268,6 +268,12 @@ module.exports = React.createClass({
|
||||||
but for now be warned.
|
but for now be warned.
|
||||||
</div>,
|
</div>,
|
||||||
button: "Sign out",
|
button: "Sign out",
|
||||||
|
extraButtons: [
|
||||||
|
<button className="mx_Dialog_primary"
|
||||||
|
onClick={this._onExportE2eKeysClicked}>
|
||||||
|
Export E2E room keys
|
||||||
|
</button>
|
||||||
|
],
|
||||||
onFinished: (confirmed) => {
|
onFinished: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
dis.dispatch({action: 'logout'});
|
dis.dispatch({action: 'logout'});
|
||||||
|
|
|
@ -185,7 +185,6 @@ module.exports = React.createClass({
|
||||||
const teamToken = data.team_token;
|
const teamToken = data.team_token;
|
||||||
// Store for use /w welcome pages
|
// Store for use /w welcome pages
|
||||||
window.localStorage.setItem('mx_team_token', teamToken);
|
window.localStorage.setItem('mx_team_token', teamToken);
|
||||||
this.props.onTeamMemberRegistered(teamToken);
|
|
||||||
|
|
||||||
this._rtsClient.getTeam(teamToken).then((team) => {
|
this._rtsClient.getTeam(teamToken).then((team) => {
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import Lifecycle from '../../../Lifecycle';
|
import * as Lifecycle from '../../../Lifecycle';
|
||||||
import Velocity from 'velocity-vector';
|
import Velocity from 'velocity-vector';
|
||||||
|
|
||||||
export default class DeactivateAccountDialog extends React.Component {
|
export default class DeactivateAccountDialog extends React.Component {
|
||||||
|
|
|
@ -21,10 +21,8 @@ export default React.createClass({
|
||||||
displayName: 'QuestionDialog',
|
displayName: 'QuestionDialog',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string,
|
||||||
description: React.PropTypes.oneOfType([
|
description: React.PropTypes.node,
|
||||||
React.PropTypes.element,
|
extraButtons: React.PropTypes.node,
|
||||||
React.PropTypes.string,
|
|
||||||
]),
|
|
||||||
button: React.PropTypes.string,
|
button: React.PropTypes.string,
|
||||||
focus: React.PropTypes.bool,
|
focus: React.PropTypes.bool,
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
@ -34,6 +32,7 @@ export default React.createClass({
|
||||||
return {
|
return {
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
extraButtons: null,
|
||||||
button: "OK",
|
button: "OK",
|
||||||
focus: true,
|
focus: true,
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
|
@ -67,6 +66,7 @@ export default React.createClass({
|
||||||
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
||||||
{this.props.button}
|
{this.props.button}
|
||||||
</button>
|
</button>
|
||||||
|
{this.props.extraButtons}
|
||||||
{cancelButton}
|
{cancelButton}
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -435,10 +435,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
let avatarSize;
|
let avatarSize;
|
||||||
let needsSenderProfile;
|
let needsSenderProfile;
|
||||||
|
|
||||||
if (isRedacted) {
|
if (this.props.tileShape === "notif") {
|
||||||
avatarSize = 0;
|
|
||||||
needsSenderProfile = false;
|
|
||||||
} else if (this.props.tileShape === "notif") {
|
|
||||||
avatarSize = 24;
|
avatarSize = 24;
|
||||||
needsSenderProfile = true;
|
needsSenderProfile = true;
|
||||||
} else if (isInfoMessage) {
|
} else if (isInfoMessage) {
|
||||||
|
@ -503,8 +500,8 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
else if (e2eEnabled) {
|
else if (e2eEnabled) {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||||
}
|
}
|
||||||
const timestamp = this.props.mxEvent.isRedacted() ?
|
const timestamp = this.props.mxEvent.getTs() ?
|
||||||
null : <MessageTimestamp ts={this.props.mxEvent.getTs()} />;
|
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
|
||||||
if (this.props.tileShape === "notif") {
|
if (this.props.tileShape === "notif") {
|
||||||
var room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
var room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
|
|
@ -485,11 +485,12 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
|
||||||
label="People"
|
label="People"
|
||||||
editable={ false }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
|
alwaysShowHeader={ true }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
|
|
@ -56,8 +56,7 @@ module.exports = React.createClass({
|
||||||
return({
|
return({
|
||||||
hover : false,
|
hover : false,
|
||||||
badgeHover : false,
|
badgeHover : false,
|
||||||
notificationTagMenu: false,
|
menuDisplayed: false,
|
||||||
roomTagMenu: false,
|
|
||||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -136,62 +135,32 @@ module.exports = React.createClass({
|
||||||
this.setState({ hover: false });
|
this.setState({ hover: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
var NotificationStateMenu = sdk.getComponent('context_menus.NotificationStateContextMenu');
|
var RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
|
||||||
var elementRect = e.target.getBoundingClientRect();
|
var elementRect = e.target.getBoundingClientRect();
|
||||||
|
|
||||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
var x = elementRect.right + window.pageXOffset + 3;
|
const x = elementRect.right + window.pageXOffset + 3;
|
||||||
var y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset) - 53;
|
const chevronOffset = 12;
|
||||||
|
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
|
||||||
|
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
ContextualMenu.createMenu(NotificationStateMenu, {
|
ContextualMenu.createMenu(RoomTileContextMenu, {
|
||||||
menuWidth: 188,
|
chevronOffset: chevronOffset,
|
||||||
menuHeight: 126,
|
|
||||||
chevronOffset: 45,
|
|
||||||
left: x,
|
left: x,
|
||||||
top: y,
|
top: y,
|
||||||
room: this.props.room,
|
room: this.props.room,
|
||||||
onFinished: function() {
|
onFinished: function() {
|
||||||
self.setState({ notificationTagMenu: false });
|
self.setState({ menuDisplayed: false });
|
||||||
self.props.refreshSubList();
|
self.props.refreshSubList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.setState({ notificationTagMenu: true });
|
this.setState({ menuDisplayed: true });
|
||||||
}
|
}
|
||||||
// Prevent the RoomTile onClick event firing as well
|
// Prevent the RoomTile onClick event firing as well
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
},
|
},
|
||||||
|
|
||||||
onAvatarClicked: function(e) {
|
|
||||||
// Only allow none guests to access the context menu
|
|
||||||
if (!MatrixClientPeg.get().isGuest() && !this.props.collapsed) {
|
|
||||||
|
|
||||||
// If the badge is clicked, then no longer show tooltip
|
|
||||||
if (this.props.collapsed) {
|
|
||||||
this.setState({ hover: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
var RoomTagMenu = sdk.getComponent('context_menus.RoomTagContextMenu');
|
|
||||||
var elementRect = e.target.getBoundingClientRect();
|
|
||||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
|
||||||
var x = elementRect.right + window.pageXOffset + 3;
|
|
||||||
var y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset) - 19;
|
|
||||||
var self = this;
|
|
||||||
ContextualMenu.createMenu(RoomTagMenu, {
|
|
||||||
chevronOffset: 10,
|
|
||||||
// XXX: fix horrid hardcoding
|
|
||||||
menuColour: UserSettingsStore.getSyncedSettings().theme === 'dark' ? "#2d2d2d" : "#FFFFFF",
|
|
||||||
left: x,
|
|
||||||
top: y,
|
|
||||||
room: this.props.room,
|
|
||||||
onFinished: function() {
|
|
||||||
self.setState({ roomTagMenu: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ roomTagMenu: true });
|
|
||||||
// Prevent the RoomTile onClick event firing as well
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
var me = this.props.room.currentState.members[myUserId];
|
var me = this.props.room.currentState.members[myUserId];
|
||||||
|
@ -210,7 +179,7 @@ module.exports = React.createClass({
|
||||||
'mx_RoomTile_unreadNotify': notifBadges,
|
'mx_RoomTile_unreadNotify': notifBadges,
|
||||||
'mx_RoomTile_highlight': mentionBadges,
|
'mx_RoomTile_highlight': mentionBadges,
|
||||||
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
||||||
'mx_RoomTile_notificationTagMenu': this.state.notificationTagMenu,
|
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
||||||
'mx_RoomTile_noBadges': !badges,
|
'mx_RoomTile_noBadges': !badges,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -218,14 +187,9 @@ module.exports = React.createClass({
|
||||||
'mx_RoomTile_avatar': true,
|
'mx_RoomTile_avatar': true,
|
||||||
});
|
});
|
||||||
|
|
||||||
var avatarContainerClasses = classNames({
|
|
||||||
'mx_RoomTile_avatar_container': true,
|
|
||||||
'mx_RoomTile_avatar_roomTagMenu': this.state.roomTagMenu,
|
|
||||||
});
|
|
||||||
|
|
||||||
var badgeClasses = classNames({
|
var badgeClasses = classNames({
|
||||||
'mx_RoomTile_badge': true,
|
'mx_RoomTile_badge': true,
|
||||||
'mx_RoomTile_badgeButton': this.state.badgeHover || this.state.notificationTagMenu,
|
'mx_RoomTile_badgeButton': this.state.badgeHover || this.state.menuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
// XXX: We should never display raw room IDs, but sometimes the
|
// XXX: We should never display raw room IDs, but sometimes the
|
||||||
|
@ -236,7 +200,7 @@ module.exports = React.createClass({
|
||||||
var badge;
|
var badge;
|
||||||
var badgeContent;
|
var badgeContent;
|
||||||
|
|
||||||
if (this.state.badgeHover || this.state.notificationTagMenu) {
|
if (this.state.badgeHover || this.state.menuDisplayed) {
|
||||||
badgeContent = "\u00B7\u00B7\u00B7";
|
badgeContent = "\u00B7\u00B7\u00B7";
|
||||||
} else if (badges) {
|
} else if (badges) {
|
||||||
var limitedCount = FormattingUtils.formatCount(notificationCount);
|
var limitedCount = FormattingUtils.formatCount(notificationCount);
|
||||||
|
@ -254,7 +218,7 @@ module.exports = React.createClass({
|
||||||
var nameClasses = classNames({
|
var nameClasses = classNames({
|
||||||
'mx_RoomTile_name': true,
|
'mx_RoomTile_name': true,
|
||||||
'mx_RoomTile_invite': this.props.isInvite,
|
'mx_RoomTile_invite': this.props.isInvite,
|
||||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.notificationTagMenu,
|
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.selected) {
|
if (this.props.selected) {
|
||||||
|
@ -293,11 +257,9 @@ module.exports = React.createClass({
|
||||||
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
||||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<div className={avatarClasses}>
|
<div className={avatarClasses}>
|
||||||
<div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}>
|
<div className="mx_RoomTile_avatar_container">
|
||||||
<div className={avatarContainerClasses}>
|
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
{directMessageIndicator}
|
||||||
{directMessageIndicator}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomTile_nameContainer">
|
<div className="mx_RoomTile_nameContainer">
|
||||||
|
|
|
@ -68,48 +68,49 @@ describe('InteractiveAuthDialog', function () {
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
/>, parentDiv);
|
/>, parentDiv);
|
||||||
|
|
||||||
// at this point there should be a password box and a submit button
|
// wait for a password box and a submit button
|
||||||
const formNode = ReactTestUtils.findRenderedDOMComponentWithTag(dlg, "form");
|
test_utils.waitForRenderedDOMComponentWithTag(dlg, "form").then((formNode) => {
|
||||||
const inputNodes = ReactTestUtils.scryRenderedDOMComponentsWithTag(
|
const inputNodes = ReactTestUtils.scryRenderedDOMComponentsWithTag(
|
||||||
dlg, "input"
|
dlg, "input"
|
||||||
);
|
);
|
||||||
let passwordNode;
|
let passwordNode;
|
||||||
let submitNode;
|
let submitNode;
|
||||||
for (const node of inputNodes) {
|
for (const node of inputNodes) {
|
||||||
if (node.type == 'password') {
|
if (node.type == 'password') {
|
||||||
passwordNode = node;
|
passwordNode = node;
|
||||||
} else if (node.type == 'submit') {
|
} else if (node.type == 'submit') {
|
||||||
submitNode = node;
|
submitNode = node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
expect(passwordNode).toExist();
|
||||||
expect(passwordNode).toExist();
|
expect(submitNode).toExist();
|
||||||
expect(submitNode).toExist();
|
|
||||||
|
|
||||||
// submit should be disabled
|
// submit should be disabled
|
||||||
expect(submitNode.disabled).toBe(true);
|
expect(submitNode.disabled).toBe(true);
|
||||||
|
|
||||||
// put something in the password box, and hit enter; that should
|
// put something in the password box, and hit enter; that should
|
||||||
// trigger a request
|
// trigger a request
|
||||||
passwordNode.value = "s3kr3t";
|
passwordNode.value = "s3kr3t";
|
||||||
ReactTestUtils.Simulate.change(passwordNode);
|
ReactTestUtils.Simulate.change(passwordNode);
|
||||||
expect(submitNode.disabled).toBe(false);
|
expect(submitNode.disabled).toBe(false);
|
||||||
ReactTestUtils.Simulate.submit(formNode, {});
|
ReactTestUtils.Simulate.submit(formNode, {});
|
||||||
|
|
||||||
expect(doRequest.callCount).toEqual(1);
|
expect(doRequest.callCount).toEqual(1);
|
||||||
expect(doRequest.calledWithExactly({
|
expect(doRequest.calledWithExactly({
|
||||||
session: "sess",
|
session: "sess",
|
||||||
type: "m.login.password",
|
type: "m.login.password",
|
||||||
password: "s3kr3t",
|
password: "s3kr3t",
|
||||||
user: "@user:id",
|
user: "@user:id",
|
||||||
})).toBe(true);
|
})).toBe(true);
|
||||||
|
|
||||||
// there should now be a spinner
|
// there should now be a spinner
|
||||||
ReactTestUtils.findRenderedComponentWithType(
|
ReactTestUtils.findRenderedComponentWithType(
|
||||||
dlg, sdk.getComponent('elements.Spinner'),
|
dlg, sdk.getComponent('elements.Spinner'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// let the request complete
|
// let the request complete
|
||||||
q.delay(1).then(() => {
|
return q.delay(1);
|
||||||
|
}).then(() => {
|
||||||
expect(onFinished.callCount).toEqual(1);
|
expect(onFinished.callCount).toEqual(1);
|
||||||
expect(onFinished.calledWithExactly(true, {a:1})).toBe(true);
|
expect(onFinished.calledWithExactly(true, {a:1})).toBe(true);
|
||||||
}).done(done, done);
|
}).done(done, done);
|
||||||
|
|
|
@ -1,11 +1,51 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sinon = require('sinon');
|
import sinon from 'sinon';
|
||||||
var q = require('q');
|
import q from 'q';
|
||||||
|
import ReactTestUtils from 'react-addons-test-utils';
|
||||||
|
|
||||||
var peg = require('../src/MatrixClientPeg.js');
|
import peg from '../src/MatrixClientPeg.js';
|
||||||
var jssdk = require('matrix-js-sdk');
|
import jssdk from 'matrix-js-sdk';
|
||||||
var MatrixEvent = jssdk.MatrixEvent;
|
const MatrixEvent = jssdk.MatrixEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around window.requestAnimationFrame that returns a promise
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _waitForFrame() {
|
||||||
|
const def = q.defer();
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
def.resolve();
|
||||||
|
});
|
||||||
|
return def.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits a small number of animation frames for a component to appear
|
||||||
|
* in the DOM. Like findRenderedDOMComponentWithTag(), but allows
|
||||||
|
* for the element to appear a short time later, eg. if a promise needs
|
||||||
|
* to resolve first.
|
||||||
|
* @return a promise that resolves once the component appears, or rejects
|
||||||
|
* if it doesn't appear after a nominal number of animation frames.
|
||||||
|
*/
|
||||||
|
export function waitForRenderedDOMComponentWithTag(tree, tag, attempts) {
|
||||||
|
if (attempts === undefined) {
|
||||||
|
// Let's start by assuming we'll only need to wait a single frame, and
|
||||||
|
// we can try increasing this if necessary.
|
||||||
|
attempts = 1;
|
||||||
|
} else if (attempts == 0) {
|
||||||
|
return q.reject("Gave up waiting for component with tag: " + tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _waitForFrame().then(() => {
|
||||||
|
const result = ReactTestUtils.scryRenderedDOMComponentsWithTag(tree, tag);
|
||||||
|
if (result.length > 0) {
|
||||||
|
return result[0];
|
||||||
|
} else {
|
||||||
|
return waitForRenderedDOMComponentWithTag(tree, tag, attempts - 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform common actions before each test case, e.g. printing the test case
|
* Perform common actions before each test case, e.g. printing the test case
|
||||||
|
|
Loading…
Reference in a new issue