experiment with trying to turn UserSettings into a controller-less 'wiring component' which wires together a series of smaller components (in this case, so small they're mainly <input/s>

This commit is contained in:
Matthew Hodgson 2015-11-17 02:15:55 +00:00
parent 88c5a5e074
commit 7b3eea0b58
5 changed files with 334 additions and 51 deletions

View file

@ -94,7 +94,16 @@ limitations under the License.
font-size: 24px;
font-weight: bold;
overflow: hidden;
margin-left: 63px;
text-overflow: ellipsis;
width: 100%;
}
.mx_RoomHeader_simpleHeaderCancel {
float: right;
margin-top: 8px;
padding: 24px;
cursor: pointer;
}
.mx_RoomHeader_name {

View file

@ -19,3 +19,101 @@ limitations under the License.
margin-left: auto;
margin-right: auto;
}
.mx_UserSettings_spinner {
display: inline-block;
vertical-align: middle;
margin-right: 12px;
width: 32px;
height: 32px;
}
.mx_UserSettings_button {
display: inline;
vertical-align: middle;
border: 0px;
height: 36px;
border-radius: 36px;
font-weight: 400;
font-size: 16px;
color: #fff;
background-color: #76cfa6;
width: auto;
margin: auto;
padding: 7px;
padding-left: 1.5em;
padding-right: 1.5em;
cursor: pointer;
}
.mx_UserSettings h2 {
clear: both;
margin-top: 32px;
margin-bottom: 8px;
margin-left: 63px;
padding-bottom: 6px;
border-bottom: 1px solid #eee;
}
.mx_UserSettings_section {
margin-left: 63px;
margin-top: 28px;
margin-bottom: 28px;
}
.mx_UserSettings_profileTable,
.mx_UserSettings_notifTable
{
display: table;
}
.mx_UserSettings_profileTableRow,
.mx_UserSettings_notifTableRow
{
display: table-row;
}
.mx_UserSettings_profileLabelCell
{
padding-bottom: 21px;
display: table-cell;
font-weight: bold;
padding-right: 24px;
}
.mx_UserSettings_profileInputCell {
display: table-cell;
padding-bottom: 21px;
width: 240px;
}
.mx_UserSettings_profileInputCell input {
border: 0px;
border-bottom: 1px solid rgba(151, 151, 151, 0.5);
padding: 0px;
width: 240px;
color: rgba(74, 74, 74, 0.9);
font-family: 'Myriad Pro', Helvetica, Arial, Sans-Serif;
font-size: 16px;
}
.mx_UserSettings_notifInputCell {
display: table-cell;
padding-bottom: 21px;
padding-right: 8px;
width: 16px;
}
.mx_UserSettings_notifLabelCell
{
padding-bottom: 21px;
width: 270px;
display: table-cell;
}
.mx_UserSettings_logout,
.mx_UserSettings_save {
float: right;
margin-right: 24px;
margin-bottom: 24px;
}

View file

@ -48,10 +48,15 @@ module.exports = React.createClass({
var header;
if (this.props.simpleHeader) {
var cancel;
if (this.props.onCancelClick) {
cancel = <img className="mx_RoomHeader_simpleHeaderCancel" src="img/cancel-black.png" onClick={ this.props.onCancelClick } alt="Close" width="18" height="18"/>
}
header =
<div className="mx_RoomHeader_wrapper">
<div className="mx_RoomHeader_simpleHeader">
{ this.props.simpleHeader }
{ cancel }
</div>
</div>
}

View file

@ -15,15 +15,142 @@ limitations under the License.
var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher')
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var q = require('q');
var UserSettingsController = require('matrix-react-sdk/lib/controllers/organisms/UserSettings')
var version = require('../../../../../package.json').version;
var Modal = require('matrix-react-sdk/lib/Modal');
var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
module.exports = React.createClass({
displayName: 'UserSettings',
mixins: [UserSettingsController],
Phases: {
Loading: "loading",
Saving: "saving",
Display: "display",
},
getInitialState: function() {
return {
avatarUrl: null,
displayName: null,
threePids: [],
clientVersion: version,
phase: this.Phases.Loading,
};
},
componentWillMount: function() {
var self = this;
var profilePromise = UserSettingsStore.loadProfileInfo();
var threepidPromise = UserSettingsStore.loadThreePids();
q.all([profilePromise, threepidPromise]).then(
function(resps) {
self.setState({
avatarUrl: resps[0].avatar_url,
displayName: resps[0].displayname,
threepids: resps[1].threepids,
phase: self.Phases.Display,
});
// keep a copy of the original state in order to track changes
self.setState({
originalState: self.state
});
},
function(error) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Can't load user settings",
description: error.toString()
});
}
);
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onSaveClicked: function(ev) {
var self = this;
var savePromises = [];
// XXX: this is managed in ChangeAvatar.js, although could be moved out here in order
// to allow for the change to be staged alongside the rest of the form.
//
// if (this.state.originalState.avatarUrl !== this.state.avatarUrl) {
// savePromises.push( UserSettingsStore.saveAvatarUrl(this.state.avatarUrl) );
// }
if (this.state.originalState.displayName !== this.state.displayName) {
savePromises.push( UserSettingsStore.saveDisplayName(this.state.displayName) );
}
if (this.state.originalState.threepids.length !== this.state.threepids.length ||
this.state.originalState.threepids.every(function(element, index) {
return element === this.state.threepids[index];
}))
{
savePromises.push( UserSettingsStore.saveThreePids(this.state.threepids) );
}
self.setState({
phase: self.Phases.Saving,
});
q.all(savePromises).then(
function(resps) {
self.setState({
phase: self.Phases.Display,
});
self.onClose();
},
function(error) {
self.setState({
phase: self.Phases.Display,
});
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Can't save user settings",
description: error.toString()
});
}
);
},
onClose: function(ev) {
// XXX: use browser history instead to find the previous room?
if (this.props.roomId) {
dis.dispatch({
action: 'view_room',
room_id: this.props.roomId,
});
}
else {
dis.dispatch({
action: 'view_indexed_room',
roomIndex: 0,
});
}
},
onAction: function(payload) {
if (payload.action === "notifier_enabled") {
this.setState({
enableNotifications : UserSettingsStore.getEnableNotifications()
});
}
},
editAvatar: function() {
var url = MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl);
@ -39,20 +166,11 @@ module.exports = React.createClass({
this.avatarDialog = Modal.createDialogWithElement(avatarDialog);
},
addEmail: function() {
onAvatarDialogCancel: function() {
this.avatarDialog.close();
},
editDisplayName: function() {
this.refs.displayname.edit();
},
changePassword: function() {
var ChangePassword = sdk.getComponent('molecules.ChangePassword');
Modal.createDialog(ChangePassword);
},
onLogoutClicked: function(ev) {
onLogoutClicked: function(event) {
var LogoutPrompt = sdk.getComponent('organisms.LogoutPrompt');
this.logoutModal = Modal.createDialog(LogoutPrompt, {onCancel: this.onLogoutPromptCancel});
},
@ -61,61 +179,114 @@ module.exports = React.createClass({
this.logoutModal.closeDialog();
},
onAvatarDialogCancel: function() {
this.avatarDialog.close();
onDisplayNameChange: function(event) {
this.setState({ displayName: event.target.value });
},
onEnableNotificationsChange: function(event) {
// don't bother waiting for Save to be clicked, as that'd be silly
UserSettingsStore.setEnableNotifications( this.refs.enableNotifications.value );
this.setState({
enableNotifications : UserSettingsStore.getEnableNotifications()
});
},
render: function() {
var Loader = sdk.getComponent("atoms.Spinner");
var saving;
switch (this.state.phase) {
case this.Phases.Loading:
return <Loader />
case this.Phases.Saving:
saving = <Loader />
case this.Phases.Display:
var ChangeDisplayName = sdk.getComponent('molecules.ChangeDisplayName');
var EnableNotificationsButton = sdk.getComponent('atoms.EnableNotificationsButton');
var RoomHeader = sdk.getComponent('molecules.RoomHeader');
return (
<div className="mx_UserSettings">
<div className="mx_UserSettings_User">
<h1>User Settings</h1>
<hr/>
<div className="mx_UserSettings_User_Inner">
<div className="mx_UserSettings_Avatar">
<div className="mx_UserSettings_Avatar_Text">Profile Photo</div>
<div className="mx_UserSettings_Avatar_Edit" onClick={this.editAvatar}>Edit</div>
<RoomHeader simpleHeader="Settings" onCancelClick={ this.onClose } />
<h2>Profile</h2>
<div className="mx_UserSettings_section">
<div className="mx_UserSettings_profileTable">
<div className="mx_UserSettings_profileTableRow">
<div className="mx_UserSettings_profileLabelCell">
<label htmlFor="displayName">Display name</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<input id="displayName" ref="displayName" value={ this.state.displayName } onChange={ this.onDisplayNameChange } />
</div>
</div>
<div className="mx_UserSettings_DisplayName">
<ChangeDisplayName ref="displayname" />
<div className="mx_UserSettings_DisplayName_Edit" onClick={this.editDisplayName}>Edit</div>
</div>
<div className="mx_UserSettings_3pids">
{this.state.threepids.map(function(val) {
return <div key={val.address}>{val.address}</div>;
var id = "email-" + val.address;
return (
<div className="mx_UserSettings_profileTableRow">
<div className="mx_UserSettings_profileLabelCell">
<label htmlFor={ id }>Email</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<input key={val.address} id={ id } value={ val.address } disabled />
</div>
</div>
);
})}
</div>
<div className="mx_UserSettings_Add3pid" onClick={this.addEmail}>Add email</div>
<div className="mx_UserSettings_profileTableRow">
<div className="mx_UserSettings_profileLabelCell">
<label htmlFor="password1">New password</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<input id="password1" ref="password1" value={ this.state.password1 } />
</div>
</div>
<div className="mx_UserSettings_profileTableRow">
<div className="mx_UserSettings_profileLabelCell">
<label htmlFor="password2">Confirm new password</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<input id="password2" ref="password2" value={ this.state.password2 } />
</div>
</div>
<div className="mx_UserSettings_Global">
<h1>Global Settings</h1>
<hr/>
<div className="mx_UserSettings_Global_Inner">
<div className="mx_UserSettings_ChangePassword" onClick={this.changePassword}>
Change Password
</div>
<div className="mx_UserSettings_ClientVersion">
<div className="mx_UserSettings_avatarPicker">
<div className="mx_UserSettings_avatarPicker_edit" onClick={this.editAvatar}></div>
</div>
</div>
<div className="mx_UserSettings_logout">
<div className="mx_UserSettings_button" onClick={this.onLogoutClicked}>Log out</div>
</div>
<h2>Notifications</h2>
<div className="mx_UserSettings_section">
<div className="mx_UserSettings_notifTable">
<div className="mx_UserSettings_notifTableRow">
<div className="mx_UserSettings_notifInputCell">
<input id="enableNotifications" ref="enableNotifications" type="checkbox" checked={ this.state.enableNotifications } onChange={ this.onEnableNotificationsChange } />
</div>
<div className="mx_UserSettings_notifLabelCell">
<label htmlFor="enableNotifications">Enable desktop notifications</label>
</div>
</div>
</div>
</div>
<h2>Advanced</h2>
<div className="mx_UserSettings_section">
<div className="mx_UserSettings_advanced">
Version {this.state.clientVersion}
</div>
<div className="mx_UserSettings_EnableNotifications">
<EnableNotificationsButton />
</div>
<div className="mx_UserSettings_Logout">
<button onClick={this.onLogoutClicked}>Sign Out</button>
</div>
</div>
<div className="mx_UserSettings_save">
<div className="mx_UserSettings_spinner">{ saving }</div>
<div className="mx_UserSettings_button" onClick={this.onSaveClicked}>Save and close</div>
</div>
</div>
);

View file

@ -111,7 +111,7 @@ module.exports = React.createClass({
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} />
break;
case this.PageTypes.UserSettings:
page_element = <UserSettings />
page_element = <UserSettings roomId={this.state.currentRoom} />
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
break;
case this.PageTypes.CreateRoom: