diff --git a/skins/base/css/common.css b/skins/base/css/common.css index ba2f4eab6..4c2e7c0b8 100644 --- a/skins/base/css/common.css +++ b/skins/base/css/common.css @@ -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; +} diff --git a/skins/base/css/organisms/RoomView.css b/skins/base/css/organisms/RoomView.css index 9a9e6b4ae..bc1a16927 100644 --- a/skins/base/css/organisms/RoomView.css +++ b/skins/base/css/organisms/RoomView.css @@ -24,6 +24,7 @@ limitations under the License. display: -webkit-flex; display: flex; width: 100%; + height: 100%; flex-direction: column; -webkit-flex-direction: column; } @@ -65,7 +66,7 @@ limitations under the License. margin-bottom: 60px; /* background-color: #ff0; */ - overflow-y: scroll; + overflow-y: scroll; } .mx_RoomView_messageListWrapper { diff --git a/skins/base/css/organisms/UserSettings.css b/skins/base/css/organisms/UserSettings.css new file mode 100644 index 000000000..eca55b553 --- /dev/null +++ b/skins/base/css/organisms/UserSettings.css @@ -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; +} diff --git a/skins/base/css/pages/MatrixChat.css b/skins/base/css/pages/MatrixChat.css index 5ff442cf2..544204116 100644 --- a/skins/base/css/pages/MatrixChat.css +++ b/skins/base/css/pages/MatrixChat.css @@ -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; @@ -47,7 +47,7 @@ limitations under the License. background-color: #f3f8fa; width: 100%; - height: 100%; + height: 100%; } .mx_MatrixChat .mx_RightPanel { @@ -60,5 +60,5 @@ limitations under the License. background-color: #f3f8fa; -webkit-flex: 0 0 230px; flex: 0 0 230px; - height: 100%; + height: 100%; } diff --git a/skins/base/views/atoms/EditableText.js b/skins/base/views/atoms/EditableText.js index a8f55814e..07bdc911d 100644 --- a/skins/base/views/atoms/EditableText.js +++ b/skins/base/views/atoms/EditableText.js @@ -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 =
{this.state.value}
; + if (this.state.value) { + editable_el =
{this.state.value}
; + } else { + editable_el =
{this.props.placeHolder}
; + } } else if (this.state.phase == this.Phases.Edit) { editable_el = (
diff --git a/skins/base/views/molecules/ChangeAvatar.js b/skins/base/views/molecules/ChangeAvatar.js new file mode 100644 index 000000000..021b4ad6e --- /dev/null +++ b/skins/base/views/molecules/ChangeAvatar.js @@ -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 ( +
+ +
+ + +
+
+ ); + case this.Phases.Uploading: + return ( + + ); + } + } +}); diff --git a/skins/base/views/molecules/ChangePassword.js b/skins/base/views/molecules/ChangePassword.js new file mode 100644 index 000000000..969a43d8f --- /dev/null +++ b/skins/base/views/molecules/ChangePassword.js @@ -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 ( +
+
{this.state.errorString}
+ + + +
+ + +
+
+ ); + case this.Phases.Uploading: + return ( + + ); + case this.Phases.Success: + return ( +
+ Success! + +
+ ) + } + } +}); diff --git a/skins/base/views/molecules/DirectoryMenu.js b/skins/base/views/molecules/DirectoryMenu.js index b6c0f8080..f3b9e592b 100644 --- a/skins/base/views/molecules/DirectoryMenu.js +++ b/skins/base/views/molecules/DirectoryMenu.js @@ -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 (
@@ -42,7 +49,7 @@ module.exports = React.createClass({
Directory
-
+
diff --git a/skins/base/views/organisms/UserSettings.js b/skins/base/views/organisms/UserSettings.js new file mode 100644 index 000000000..5bce0a036 --- /dev/null +++ b/skins/base/views/organisms/UserSettings.js @@ -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 + case this.Phases.Display: + return ( +
+
+

User Settings

+
+
+
+
Profile Photo
+
Edit
+
+ +
+ +
Edit
+
+ +
+ {this.state.threepids.map(function(val) { + return
{val.address}
; + })} +
+ +
Add email
+
+
+ +
+

Global Settings

+
+
+
+ Change Password +
+
+ Version {this.state.clientVersion} +
+
+
+
+ ); + } + } +}); diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index b9b5a1a94..27d1a1151 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -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 = + right_panel = + } else if (this.state.page_type == this.PageTypes.UserSettings) { + page_element = + } + return (
- - +
+ {page_element} +
+ {right_panel}
); } else if (this.state.logged_in) { @@ -63,4 +77,3 @@ module.exports = React.createClass({ } } }); - diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index 32587c628..dfbcf2e21 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -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'); diff --git a/src/Modal.js b/src/Modal.js new file mode 100644 index 000000000..ca11a2101 --- /dev/null +++ b/src/Modal.js @@ -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 = ( +
+
+ +
+
+
+ ); + + React.render(dialog, this.getOrCreateContainer()); + }, +}; diff --git a/src/controllers/atoms/EditableText.js b/src/controllers/atoms/EditableText.js index ac4697361..d9155a5cb 100644 --- a/src/controllers/atoms/EditableText.js +++ b/src/controllers/atoms/EditableText.js @@ -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() { diff --git a/src/controllers/molecules/ChangeAvatar.js b/src/controllers/molecules/ChangeAvatar.js new file mode 100644 index 000000000..2dd492b6e --- /dev/null +++ b/src/controllers/molecules/ChangeAvatar.js @@ -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() { + + }, +} diff --git a/src/controllers/molecules/ChangePassword.js b/src/controllers/molecules/ChangePassword.js new file mode 100644 index 000000000..5cc73c5de --- /dev/null +++ b/src/controllers/molecules/ChangePassword.js @@ -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() + }) + }); + }, +} diff --git a/src/controllers/organisms/UserSettings.js b/src/controllers/organisms/UserSettings.js new file mode 100644 index 000000000..80056b236 --- /dev/null +++ b/src/controllers/organisms/UserSettings.js @@ -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); } + ); + }, +} diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index 44a4df075..9367872b2 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -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 = { } } }; -