This commit is contained in:
Matthew Hodgson 2017-02-02 22:05:44 +00:00
commit be41462f3a
35 changed files with 621 additions and 275 deletions

View file

@ -105,6 +105,7 @@ var FilePanel = React.createClass({
showUrlPreview = { false }
tileShape="file_grid"
opacity={ this.props.opacity }
empty="There are no visible files in this room"
/>
);
}

View file

@ -171,6 +171,7 @@ export default React.createClass({
brand={this.props.config.brand}
collapsedRhs={this.props.collapse_rhs}
enableLabs={this.props.config.enableLabs}
referralBaseUrl={this.props.config.referralBaseUrl}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
break;

View file

@ -1055,12 +1055,13 @@ module.exports = React.createClass({
sessionId={this.state.register_session_id}
idSid={this.state.register_id_sid}
email={this.props.startingFragmentQueryParams.email}
referrer={this.props.startingFragmentQueryParams.referrer}
username={this.state.upgradeUsername}
guestAccessToken={this.state.guestAccessToken}
defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()}
brand={this.props.config.brand}
teamsConfig={this.props.config.teamsConfig}
teamServerConfig={this.props.config.teamServerConfig}
customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()}
registrationUrl={this.props.registrationUrl}

View file

@ -48,6 +48,7 @@ var NotificationPanel = React.createClass({
showUrlPreview = { false }
opacity={ this.props.opacity }
tileShape="notif"
empty="You have no visible notifications"
/>
);
}

View file

@ -74,6 +74,7 @@ module.exports = React.createClass({
// callback for when the status bar can be hidden from view, as it is
// not displaying anything
onHidden: React.PropTypes.func,
// callback for when the status bar is displaying something and should
// be visible
onVisible: React.PropTypes.func,
@ -113,7 +114,9 @@ module.exports = React.createClass({
clearTimeout(this.hideDebouncer);
}
this.hideDebouncer = setTimeout(() => {
this.props.onHidden();
// temporarily stop hiding the statusbar as per
// https://github.com/vector-im/riot-web/issues/1991#issuecomment-276953915
// this.props.onHidden();
}, HIDE_DEBOUNCE_MS);
}
},
@ -238,7 +241,7 @@ module.exports = React.createClass({
if (othersCount > 0) {
avatars.push(
<span className="mx_RoomStatusBar_typingIndicatorRemaining">
<span className="mx_RoomStatusBar_typingIndicatorRemaining" key="others">
+{othersCount}
</span>
);

View file

@ -1332,12 +1332,14 @@ module.exports = React.createClass({
},
onStatusBarVisible: function() {
if (this.unmounted) return;
this.setState({
statusBarVisible: true,
});
},
onStatusBarHidden: function() {
if (this.unmounted) return;
this.setState({
statusBarVisible: false,
});
@ -1507,13 +1509,14 @@ module.exports = React.createClass({
});
var statusBar;
let isStatusAreaExpanded = true;
if (ContentMessages.getCurrentUploads().length > 0) {
var UploadBar = sdk.getComponent('structures.UploadBar');
statusBar = <UploadBar room={this.state.room} />;
} else if (!this.state.searchResults) {
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
isStatusAreaExpanded = this.state.statusBarVisible;
statusBar = <RoomStatusBar
room={this.state.room}
tabComplete={this.tabComplete}
@ -1683,7 +1686,7 @@ module.exports = React.createClass({
);
}
let statusBarAreaClass = "mx_RoomView_statusArea mx_fadable";
if (this.state.statusBarVisible) {
if (isStatusAreaExpanded) {
statusBarAreaClass += " mx_RoomView_statusArea_expanded";
}

View file

@ -25,7 +25,7 @@ var DEBUG_SCROLL = false;
// The amount of extra scroll distance to allow prior to unfilling.
// See _getExcessHeight.
const UNPAGINATION_PADDING = 1500;
const UNPAGINATION_PADDING = 3000;
// The number of milliseconds to debounce calls to onUnfillRequest, to prevent
// many scroll events causing many unfilling requests.
const UNFILL_REQUEST_DEBOUNCE_MS = 200;
@ -570,7 +570,7 @@ module.exports = React.createClass({
var boundingRect = node.getBoundingClientRect();
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
debuglog("Scrolling to token '" + node.dataset.scrollToken + "'+" +
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
pixelOffset + " (delta: "+scrollDelta+")");
if(scrollDelta != 0) {
@ -582,7 +582,7 @@ module.exports = React.createClass({
_saveScrollState: function() {
if (this.props.stickyBottom && this.isAtBottom()) {
this.scrollState = { stuckAtBottom: true };
debuglog("Saved scroll state", this.scrollState);
debuglog("ScrollPanel: Saved scroll state", this.scrollState);
return;
}
@ -601,12 +601,12 @@ module.exports = React.createClass({
trackedScrollToken: node.dataset.scrollToken,
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
};
debuglog("Saved scroll state", this.scrollState);
debuglog("ScrollPanel: saved scroll state", this.scrollState);
return;
}
}
debuglog("Unable to save scroll state: found no children in the viewport");
debuglog("ScrollPanel: unable to save scroll state: found no children in the viewport");
},
_restoreSavedScrollState: function() {
@ -640,7 +640,7 @@ module.exports = React.createClass({
this._lastSetScroll = scrollNode.scrollTop;
}
debuglog("Set scrollTop:", scrollNode.scrollTop,
debuglog("ScrollPanel: set scrollTop:", scrollNode.scrollTop,
"requested:", scrollTop,
"_lastSetScroll:", this._lastSetScroll);
},

View file

@ -96,6 +96,9 @@ var TimelinePanel = React.createClass({
// shape property to be passed to EventTiles
tileShape: React.PropTypes.string,
// placeholder text to use if the timeline is empty
empty: React.PropTypes.string,
},
statics: {
@ -990,6 +993,14 @@ var TimelinePanel = React.createClass({
);
}
if (this.state.events.length == 0) {
return (
<div className={ this.props.className + " mx_RoomView_messageListWrapper" }>
<div className="mx_RoomView_empty">{ this.props.empty }</div>
</div>
);
}
// give the messagepanel a stickybottom if we're at the end of the
// live timeline, so that the arrival of new events triggers a
// scroll.

View file

@ -104,6 +104,9 @@ module.exports = React.createClass({
// True to show the 'labs' section of experimental features
enableLabs: React.PropTypes.bool,
// 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,
},
@ -458,6 +461,27 @@ module.exports = React.createClass({
);
},
_renderReferral: function() {
const teamToken = window.localStorage.getItem('mx_team_token');
if (!teamToken) {
return null;
}
if (typeof teamToken !== 'string') {
console.warn('Team token not a string');
return null;
}
const href = (this.props.referralBaseUrl || window.location.origin) +
`/#/register?referrer=${this._me}&team_token=${teamToken}`;
return (
<div>
<h3>Referral</h3>
<div className="mx_UserSettings_section">
Refer a friend to Riot: <a href={href}>{href}</a>
</div>
</div>
);
},
_renderUserInterfaceSettings: function() {
var client = MatrixClientPeg.get();
@ -857,6 +881,8 @@ module.exports = React.createClass({
{accountJsx}
</div>
{this._renderReferral()}
{notification_area}
{this._renderUserInterfaceSettings()}

View file

@ -25,6 +25,7 @@ var ServerConfig = require("../../views/login/ServerConfig");
var MatrixClientPeg = require("../../../MatrixClientPeg");
var RegistrationForm = require("../../views/login/RegistrationForm");
var CaptchaForm = require("../../views/login/CaptchaForm");
var RtsClient = require("../../../RtsClient");
var MIN_PASSWORD_LENGTH = 6;
@ -47,23 +48,16 @@ module.exports = React.createClass({
defaultIsUrl: React.PropTypes.string,
brand: React.PropTypes.string,
email: React.PropTypes.string,
referrer: React.PropTypes.string,
username: React.PropTypes.string,
guestAccessToken: React.PropTypes.string,
teamsConfig: React.PropTypes.shape({
teamServerConfig: React.PropTypes.shape({
// Email address to request new teams
supportEmail: React.PropTypes.string,
teams: React.PropTypes.arrayOf(React.PropTypes.shape({
// The displayed name of the team
"name": React.PropTypes.string,
// The suffix with which every team email address ends
"emailSuffix": React.PropTypes.string,
// The rooms to use during auto-join
"rooms": React.PropTypes.arrayOf(React.PropTypes.shape({
"id": React.PropTypes.string,
"autoJoin": React.PropTypes.bool,
})),
})).required,
supportEmail: React.PropTypes.string.isRequired,
// URL of the riot-team-server to get team configurations and track referrals
teamServerURL: React.PropTypes.string.isRequired,
}),
teamSelected: React.PropTypes.object,
defaultDeviceDisplayName: React.PropTypes.string,
@ -75,6 +69,7 @@ module.exports = React.createClass({
getInitialState: function() {
return {
busy: false,
teamServerBusy: false,
errorText: null,
// We remember the values entered by the user because
// the registration form will be unmounted during the
@ -90,6 +85,7 @@ module.exports = React.createClass({
},
componentWillMount: function() {
this._unmounted = false;
this.dispatcherRef = dis.register(this.onAction);
// attach this to the instance rather than this.state since it isn't UI
this.registerLogic = new Signup.Register(
@ -103,10 +99,40 @@ module.exports = React.createClass({
this.registerLogic.setIdSid(this.props.idSid);
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
this.registerLogic.recheckState();
if (
this.props.teamServerConfig &&
this.props.teamServerConfig.teamServerURL &&
!this._rtsClient
) {
this._rtsClient = new RtsClient(this.props.teamServerConfig.teamServerURL);
this.setState({
teamServerBusy: true,
});
// GET team configurations including domains, names and icons
this._rtsClient.getTeamsConfig().then((data) => {
const teamsConfig = {
teams: data,
supportEmail: this.props.teamServerConfig.supportEmail,
};
console.log('Setting teams config to ', teamsConfig);
this.setState({
teamsConfig: teamsConfig,
teamServerBusy: false,
});
}, (err) => {
console.error('Error retrieving config for teams', err);
this.setState({
teamServerBusy: false,
});
});
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
this._unmounted = true;
},
componentDidMount: function() {
@ -184,24 +210,41 @@ module.exports = React.createClass({
accessToken: response.access_token
});
// Auto-join rooms
if (self.props.teamsConfig && self.props.teamsConfig.teams) {
for (let i = 0; i < self.props.teamsConfig.teams.length; i++) {
let team = self.props.teamsConfig.teams[i];
if (self.state.formVals.email.endsWith(team.emailSuffix)) {
console.log("User successfully registered with team " + team.name);
if (
self._rtsClient &&
self.props.referrer &&
self.state.teamSelected
) {
// Track referral, get team_token in order to retrieve team config
self._rtsClient.trackReferral(
self.props.referrer,
response.user_id,
self.state.formVals.email
).then((data) => {
const teamToken = data.team_token;
// Store for use /w welcome pages
window.localStorage.setItem('mx_team_token', teamToken);
self._rtsClient.getTeam(teamToken).then((team) => {
console.log(
`User successfully registered with team ${team.name}`
);
if (!team.rooms) {
break;
return;
}
// Auto-join rooms
team.rooms.forEach((room) => {
if (room.autoJoin) {
console.log("Auto-joining " + room.id);
MatrixClientPeg.get().joinRoom(room.id);
if (room.auto_join && room.room_id) {
console.log(`Auto-joining ${room.room_id}`);
MatrixClientPeg.get().joinRoom(room.room_id);
}
});
break;
}
}
}, (err) => {
console.error('Error getting team config', err);
});
}, (err) => {
console.error('Error tracking referral', err);
});
}
if (self.props.brand) {
@ -273,7 +316,15 @@ module.exports = React.createClass({
});
},
onTeamSelected: function(teamSelected) {
if (!this._unmounted) {
this.setState({ teamSelected });
}
},
_getRegisterContentJsx: function() {
const Spinner = sdk.getComponent("elements.Spinner");
var currStep = this.registerLogic.getStep();
var registerStep;
switch (currStep) {
@ -283,17 +334,23 @@ module.exports = React.createClass({
case "Register.STEP_m.login.dummy":
// NB. Our 'username' prop is specifically for upgrading
// a guest account
if (this.state.teamServerBusy) {
registerStep = <Spinner />;
break;
}
registerStep = (
<RegistrationForm
showEmail={true}
defaultUsername={this.state.formVals.username}
defaultEmail={this.state.formVals.email}
defaultPassword={this.state.formVals.password}
teamsConfig={this.props.teamsConfig}
teamsConfig={this.state.teamsConfig}
guestUsername={this.props.username}
minPasswordLength={MIN_PASSWORD_LENGTH}
onError={this.onFormValidationFailed}
onRegisterClick={this.onFormSubmit} />
onRegisterClick={this.onFormSubmit}
onTeamSelected={this.onTeamSelected}
/>
);
break;
case "Register.STEP_m.login.email.identity":
@ -322,7 +379,6 @@ module.exports = React.createClass({
}
var busySpinner;
if (this.state.busy) {
var Spinner = sdk.getComponent("elements.Spinner");
busySpinner = (
<Spinner />
);
@ -367,7 +423,7 @@ module.exports = React.createClass({
return (
<div className="mx_Login">
<div className="mx_Login_box">
<LoginHeader />
<LoginHeader icon={this.state.teamSelected ? this.state.teamSelected.icon : null}/>
{this._getRegisterContentJsx()}
<LoginFooter />
</div>