Merge pull request #5 from matrix-org/user_settings

User settings
This commit is contained in:
David Baker 2015-07-16 10:56:28 +01:00
commit 73a1c2b581
17 changed files with 603 additions and 15 deletions

View File

@ -33,3 +33,31 @@ h2 {
margin-top: 16px;
margin-bottom: 16px;
}
.mx_Dialog_Background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #ccc;
opacity: 0.5;
z-index: -200;
}
.mx_Dialog_Wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mx_Dialog {
background-color: #fff;
margin: auto;
max-width: 500px;
z-index: -100;
position: relative;
top: 100px;
}

View File

@ -24,6 +24,7 @@ limitations under the License.
display: -webkit-flex;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
-webkit-flex-direction: column;
}

View File

@ -0,0 +1,20 @@
/*
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_UserSettings {
max-width: 720px;
margin: auto;
}

View File

@ -38,7 +38,7 @@ limitations under the License.
height: 100%;
}
.mx_MatrixChat .mx_RoomView {
.mx_MatrixChat .mx_MatrixChat_MiddleView {
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;

View File

@ -43,14 +43,22 @@ module.exports = React.createClass({
},
onFinish: function(ev) {
this.setValue(ev.target.value);
if (ev.target.value) {
this.setValue(ev.target.value);
} else {
this.cancelEdit();
}
},
render: function() {
var editable_el;
if (this.state.phase == this.Phases.Display) {
editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.state.value}</div>;
if (this.state.value) {
editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.state.value}</div>;
} else {
editable_el = <div ref="display_div" onClick={this.onClickDiv}><i>{this.props.placeHolder}</i></div>;
}
} else if (this.state.phase == this.Phases.Edit) {
editable_el = (
<div>

View File

@ -0,0 +1,47 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var React = require('react');
var ChangeAvatarController = require("../../../../src/controllers/molecules/ChangeAvatar");
module.exports = React.createClass({
displayName: 'ChangeAvatar',
mixins: [ChangeAvatarController],
render: function() {
switch (this.state.phase) {
case this.Phases.Display:
case this.Phases.Error:
return (
<div>
<img src={this.state.avatarUrl} />
<div>
<button>Upload new</button>
<button onClick={this.props.onFinished}>Cancel</button>
</div>
</div>
);
case this.Phases.Uploading:
return (
<Loader />
);
}
}
});

View File

@ -0,0 +1,77 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var React = require('react');
var ChangePasswordController = require("../../../../src/controllers/molecules/ChangePassword");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'ChangePassword',
mixins: [ChangePasswordController],
onClickChange: function() {
var old_password = this.refs.old_input.getDOMNode().value;
var new_password = this.refs.new_input.getDOMNode().value;
var confirm_password = this.refs.confirm_input.getDOMNode().value;
if (new_password != confirm_password) {
this.setState({
state: this.Phases.Error,
errorString: "Passwords don't match"
});
} else if (new_password == '' || old_password == '') {
this.setState({
state: this.Phases.Error,
errorString: "Passwords can't be empty"
});
} else {
this.changePassword(old_password, new_password);
}
},
render: function() {
switch (this.state.phase) {
case this.Phases.Edit:
case this.Phases.Error:
return (
<div>
<div>{this.state.errorString}</div>
<label>Old password <input type="password" ref="old_input"/></label>
<label>New password <input type="password" ref="new_input"/></label>
<label>Confirm password <input type="password" ref="confirm_input"/></label>
<div>
<button onClick={this.onClickChange}>Change Password</button>
<button onClick={this.props.onFinished}>Cancel</button>
</div>
</div>
);
case this.Phases.Uploading:
return (
<Loader />
);
case this.Phases.Success:
return (
<div>
Success!
<button onClick={this.props.onFinished}>Ok</button>
</div>
)
}
}
});

View File

@ -19,6 +19,8 @@ limitations under the License.
var React = require('react');
var classNames = require('classnames');
var dis = require("../../../../src/dispatcher");
//var DirectoryMenuController = require("../../../../src/controllers/molecules/DirectoryMenuController");
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
@ -26,6 +28,11 @@ var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
module.exports = React.createClass({
displayName: 'DirectoryMenu',
// mixins: [DirectoryMenuController],
onSettingsClick: function() {
dis.dispatch({action: 'view_user_settings'});
},
render: function() {
return (
<div className="mx_DirectoryMenu">
@ -42,7 +49,7 @@ module.exports = React.createClass({
</div>
<div className="mx_RoomTile_name">Directory</div>
</div>
<div className="mx_RoomTile">
<div className="mx_RoomTile" onClick={this.onSettingsClick}>
<div className="mx_RoomTile_avatar">
<img src="img/settings-big.png" width="42" height="42"/>
</div>

View File

@ -0,0 +1,97 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var React = require('react');
var ComponentBroker = require('../../../../src/ComponentBroker');
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
var UserSettingsController = require("../../../../src/controllers/organisms/UserSettings");
var EditableText = ComponentBroker.get('atoms/EditableText');
var ChangeAvatar = ComponentBroker.get('molecules/ChangeAvatar');
var ChangePassword = ComponentBroker.get('molecules/ChangePassword');
var Loader = require("react-loader");
var Modal = require("../../../../src/Modal")
module.exports = React.createClass({
displayName: 'UserSettings',
mixins: [UserSettingsController],
editAvatar: function() {
var url = MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl);
Modal.createDialog(ChangeAvatar, {initialAvatarUrl: url});
},
addEmail: function() {
},
editDisplayName: function() {
this.refs.displayname.edit();
},
changePassword: function() {
Modal.createDialog(ChangePassword);
},
render: function() {
switch (this.state.phase) {
case this.Phases.Loading:
return <Loader />
case this.Phases.Display:
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>
</div>
<div className="mx_UserSettings_DisplayName">
<EditableText ref="displayname" initalValue={this.state.displayName} placeHolder="Click to set display name." onValueChanged={this.changeDisplayname}/>
<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>{val.address}</div>;
})}
</div>
<div className="mx_UserSettings_Add3pid" onClick={this.addEmail}>Add email</div>
</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">
Version {this.state.clientVersion}
</div>
</div>
</div>
</div>
);
}
}
});

View File

@ -23,6 +23,7 @@ var LeftPanel = ComponentBroker.get('organisms/LeftPanel');
var RoomView = ComponentBroker.get('organisms/RoomView');
var RightPanel = ComponentBroker.get('organisms/RightPanel');
var Login = ComponentBroker.get('templates/Login');
var UserSettings = ComponentBroker.get('organisms/UserSettings');
var Register = ComponentBroker.get('templates/Register');
var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat");
@ -37,11 +38,24 @@ module.exports = React.createClass({
render: function() {
if (this.state.logged_in && this.state.ready) {
var page_element;
var right_panel = "";
if (this.state.page_type == this.PageTypes.RoomView) {
page_element = <RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} />
right_panel = <RightPanel roomId={this.state.currentRoom} />
} else if (this.state.page_type == this.PageTypes.UserSettings) {
page_element = <UserSettings />
}
return (
<div className="mx_MatrixChat">
<LeftPanel selectedRoom={this.state.currentRoom} />
<RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} />
<RightPanel roomId={this.state.currentRoom} />
<div className="mx_MatrixChat_MiddleView">
{page_element}
</div>
{right_panel}
</div>
);
} else if (this.state.logged_in) {
@ -63,4 +77,3 @@ module.exports = React.createClass({
}
}
});

View File

@ -89,6 +89,9 @@ require('../skins/base/views/templates/Register');
require('../skins/base/views/organisms/Notifier');
require('../skins/base/views/organisms/CreateRoom');
require('../skins/base/views/molecules/UserSelector');
require('../skins/base/views/organisms/UserSettings');
require('../skins/base/views/molecules/ChangeAvatar');
require('../skins/base/views/molecules/ChangePassword');
// new for vector
require('../skins/base/views/organisms/LeftPanel');
require('../skins/base/views/organisms/RightPanel');

58
src/Modal.js Normal file
View File

@ -0,0 +1,58 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var React = require('react');
var q = require('q');
module.exports = {
DialogContainerId: "mx_Dialog_Container",
getOrCreateContainer: function() {
var container = document.getElementById(this.DialogContainerId);
if (!container) {
container = document.createElement("div");
container.id = this.DialogContainerId;
document.body.appendChild(container);
}
return container;
},
createDialog: function (Element, props) {
var self = this;
var closeDialog = function() {
React.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(arguments);
};
var dialog = (
<div className="mx_Dialog_Wrapper">
<div className="mx_Dialog">
<Element {...props} onFinished={closeDialog}/>
</div>
<div className="mx_Dialog_Background" onClick={closeDialog}></div>
</div>
);
React.render(dialog, this.getOrCreateContainer());
},
};

View File

@ -22,6 +22,7 @@ module.exports = {
propTypes: {
onValueChanged: React.PropTypes.func,
initalValue: React.PropTypes.string,
placeHolder: React.PropTypes.string,
},
Phases: {
@ -33,6 +34,7 @@ module.exports = {
return {
onValueChanged: function() {},
initalValue: '',
placeHolder: 'Click to set',
};
},
@ -51,9 +53,13 @@ module.exports = {
this.setState({
value: val,
phase: this.Phases.Display,
});
}, this.onValueChanged);
},
this.onValueChanged();
edit: function() {
this.setState({
phase: this.Phases.Edit,
});
},
cancelEdit: function() {

View File

@ -0,0 +1,52 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
module.exports = {
propTypes: {
onFinished: React.PropTypes.func,
initialAvatarUrl: React.PropTypes.string.isRequired,
},
Phases: {
Display: "display",
Uploading: "uploading",
Error: "error",
},
getDefaultProps: function() {
return {
onFinished: function() {},
};
},
getInitialState: function() {
return {
avatarUrl: this.props.initialAvatarUrl,
phase: this.Phases.Display,
}
},
uploadNewAvatar: function() {
},
}

View File

@ -0,0 +1,78 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
module.exports = {
propTypes: {
onFinished: React.PropTypes.func,
},
Phases: {
Edit: "edit",
Uploading: "uploading",
Error: "error",
Success: "Success"
},
getDefaultProps: function() {
return {
onFinished: function() {},
};
},
getInitialState: function() {
return {
phase: this.Phases.Edit,
errorString: ''
}
},
changePassword: function(old_password, new_password) {
var cli = MatrixClientPeg.get();
var authDict = {
type: 'm.login.password',
user: cli.credentials.userId,
password: old_password
};
this.setState({
phase: this.Phases.Uploading,
errorString: '',
})
var d = cli.setPassword(authDict, new_password);
var self = this;
d.then(function() {
self.setState({
phase: self.Phases.Success,
errorString: '',
})
}, function(err) {
self.setState({
phase: self.Phases.Error,
errorString: err.toString()
})
});
},
}

View File

@ -0,0 +1,82 @@
/*
Copyright 2015 OpenMarket 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.
*/
'use strict';
var MatrixClientPeg = require("../../MatrixClientPeg");
var React = require("react");
var q = require('q');
var dis = require("../../dispatcher");
var version = require('../../../package.json').version;
var ComponentBroker = require('../../ComponentBroker');
module.exports = {
Phases: {
Loading: "loading",
Display: "display",
},
getInitialState: function() {
return {
displayName: null,
avatarUrl: null,
threePids: [],
clientVersion: version,
phase: this.Phases.Loading,
};
},
changeDisplayname: function(new_displayname) {
if (this.state.displayName == new_displayname) return;
var self = this;
return MatrixClientPeg.get().setDisplayName(new_displayname).then(
function() { self.setState({displayName: new_displayname}); },
function(err) { console.err(err); }
);
},
changeAvatarUrl: function(new_avatar_url) {
if (this.state.avatarUrl == new_avatar_url) return;
var self = this;
return MatrixClientPeg.get().setAvatarUrl(new_avatar_url).then(
function() { self.setState({displayName: new_displayname}); },
function(err) { console.err(err); }
);
},
componentWillMount: function() {
var self = this;
var cli = MatrixClientPeg.get();
var profile_d = cli.getProfileInfo(cli.credentials.userId);
var threepid_d = cli.getThreePids();
q.all([profile_d, threepid_d]).then(
function(resps) {
self.setState({
displayName: resps[0].displayname,
avatarUrl: resps[0].avatar_url,
threepids: resps[1].threepids,
phase: self.Phases.Display,
});
},
function(err) { console.err(err); }
);
},
}

View File

@ -29,10 +29,16 @@ var ComponentBroker = require('../../ComponentBroker');
var Notifier = ComponentBroker.get('organisms/Notifier');
module.exports = {
PageTypes: {
RoomView: "room_view",
UserSettings: "user_settings",
},
getInitialState: function() {
return {
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
ready: false
ready: false,
page_type: this.PageTypes.RoomView,
};
},
@ -110,7 +116,8 @@ module.exports = {
case 'view_room':
this.focusComposer = true;
this.setState({
currentRoom: payload.room_id
currentRoom: payload.room_id,
page_type: this.PageTypes.RoomView,
});
break;
case 'view_prev_room':
@ -131,6 +138,11 @@ module.exports = {
currentRoom: allRooms[roomIndex].roomId
});
break;
case 'view_user_settings':
this.setState({
page_type: this.PageTypes.UserSettings,
});
break;
}
},
@ -199,4 +211,3 @@ module.exports = {
}
}
};