diff --git a/skins/base/views/atoms/EditableText.js b/skins/base/views/atoms/EditableText.js
new file mode 100644
index 0000000000..a8f55814e7
--- /dev/null
+++ b/skins/base/views/atoms/EditableText.js
@@ -0,0 +1,68 @@
+/*
+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 EditableTextController = require("../../../../src/controllers/atoms/EditableText");
+
+module.exports = React.createClass({
+ displayName: 'EditableText',
+ mixins: [EditableTextController],
+
+ onKeyUp: function(ev) {
+ if (ev.key == "Enter") {
+ this.onFinish(ev);
+ } else if (ev.key == "Escape") {
+ this.cancelEdit();
+ }
+ },
+
+ onClickDiv: function() {
+ this.setState({
+ phase: this.Phases.Edit,
+ })
+ },
+
+ onFocus: function(ev) {
+ ev.target.setSelectionRange(0, ev.target.value.length);
+ },
+
+ onFinish: function(ev) {
+ this.setValue(ev.target.value);
+ },
+
+ render: function() {
+ var editable_el;
+
+ if (this.state.phase == this.Phases.Display) {
+ editable_el =
+ );
+ }
+});
diff --git a/skins/base/views/atoms/create_room/CreateRoomButton.js b/skins/base/views/atoms/create_room/CreateRoomButton.js
new file mode 100644
index 0000000000..2f9ccae030
--- /dev/null
+++ b/skins/base/views/atoms/create_room/CreateRoomButton.js
@@ -0,0 +1,32 @@
+/*
+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 CreateRoomButtonController = require("../../../../../src/controllers/atoms/create_room/CreateRoomButton");
+
+module.exports = React.createClass({
+ displayName: 'CreateRoomButton',
+ mixins: [CreateRoomButtonController],
+
+ render: function() {
+ return (
+
+ );
+ }
+});
diff --git a/skins/base/views/atoms/create_room/Presets.js b/skins/base/views/atoms/create_room/Presets.js
new file mode 100644
index 0000000000..83fe61bdbb
--- /dev/null
+++ b/skins/base/views/atoms/create_room/Presets.js
@@ -0,0 +1,39 @@
+/*
+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 PresetsController = require("../../../../../src/controllers/atoms/create_room/Presets");
+
+module.exports = React.createClass({
+ displayName: 'CreateRoomPresets',
+ mixins: [PresetsController],
+
+ onValueChanged: function(ev) {
+ this.setState({preset: ev.target.value})
+ },
+
+ render: function() {
+ return (
+
+ );
+ }
+});
diff --git a/skins/base/views/atoms/create_room/RoomNameTextbox.js b/skins/base/views/atoms/create_room/RoomNameTextbox.js
new file mode 100644
index 0000000000..c358a14cb3
--- /dev/null
+++ b/skins/base/views/atoms/create_room/RoomNameTextbox.js
@@ -0,0 +1,36 @@
+/*
+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 RoomNameTextboxController = require("../../../../../src/controllers/atoms/create_room/RoomNameTextbox");
+
+module.exports = React.createClass({
+ displayName: 'RoomNameTextbox',
+ mixins: [RoomNameTextboxController],
+
+ onValueChanged: function(ev) {
+ this.setState({room_name: ev.target.value})
+ },
+
+ render: function() {
+ return (
+
+ );
+ }
+});
diff --git a/skins/base/views/molecules/UserSelector.js b/skins/base/views/molecules/UserSelector.js
new file mode 100644
index 0000000000..7517e29d0f
--- /dev/null
+++ b/skins/base/views/molecules/UserSelector.js
@@ -0,0 +1,44 @@
+/*
+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 UserSelectorController = require("../../../../src/controllers/molecules/UserSelector");
+
+module.exports = React.createClass({
+ displayName: 'UserSelector',
+ mixins: [UserSelectorController],
+
+ onAddUserId: function() {
+ this.addUser(this.refs.user_id_input.getDOMNode().value);
+ },
+
+ render: function() {
+ return (
+
+ );
+ }
+});
diff --git a/skins/base/views/organisms/CreateRoom.js b/skins/base/views/organisms/CreateRoom.js
new file mode 100644
index 0000000000..36f6e466e5
--- /dev/null
+++ b/skins/base/views/organisms/CreateRoom.js
@@ -0,0 +1,73 @@
+/*
+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 CreateRoomController = require("../../../../src/controllers/organisms/CreateRoom");
+
+var ComponentBroker = require('../../../../src/ComponentBroker');
+
+var CreateRoomButton = ComponentBroker.get("atoms/create_room/CreateRoomButton");
+var RoomNameTextbox = ComponentBroker.get("atoms/create_room/RoomNameTextbox");
+var Presets = ComponentBroker.get("atoms/create_room/Presets");
+var UserSelector = ComponentBroker.get("molecules/UserSelector");
+
+
+module.exports = React.createClass({
+ displayName: 'CreateRoom',
+ mixins: [CreateRoomController],
+
+ getPreset: function() {
+ return this.refs.presets.getPreset();
+ },
+
+ getName: function() {
+ return this.refs.name_textbox.getName();
+ },
+
+ getInvitedUsers: function() {
+ return this.refs.user_selector.getUserIds();
+ },
+
+ render: function() {
+ var curr_phase = this.state.phase;
+ if (curr_phase == this.phases.CREATING) {
+ return (
+
+ );
+ }
+ }
+});
diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js
index ec0f00b6ff..9bb54fa612 100644
--- a/src/ComponentBroker.js
+++ b/src/ComponentBroker.js
@@ -62,6 +62,10 @@ require('../skins/base/views/atoms/LogoutButton');
require('../skins/base/views/atoms/EnableNotificationsButton');
require('../skins/base/views/atoms/MessageTimestamp');
require('../skins/base/views/atoms/VideoFeed');
+require('../skins/base/views/atoms/create_room/CreateRoomButton');
+require('../skins/base/views/atoms/create_room/RoomNameTextbox');
+require('../skins/base/views/atoms/create_room/Presets');
+require('../skins/base/views/atoms/EditableText');
require('../skins/base/views/molecules/MatrixToolbar');
require('../skins/base/views/molecules/RoomTile');
require('../skins/base/views/molecules/MessageTile');
@@ -83,6 +87,8 @@ require('../skins/base/views/organisms/RoomList');
require('../skins/base/views/organisms/RoomView');
require('../skins/base/views/templates/Login');
require('../skins/base/views/organisms/Notifier');
+require('../skins/base/views/organisms/CreateRoom');
+require('../skins/base/views/molecules/UserSelector');
// new for vector
require('../skins/base/views/organisms/LeftPanel');
require('../skins/base/views/organisms/RightPanel');
diff --git a/src/controllers/atoms/EditableText.js b/src/controllers/atoms/EditableText.js
new file mode 100644
index 0000000000..ac46973613
--- /dev/null
+++ b/src/controllers/atoms/EditableText.js
@@ -0,0 +1,68 @@
+/*
+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');
+
+module.exports = {
+ propTypes: {
+ onValueChanged: React.PropTypes.func,
+ initalValue: React.PropTypes.string,
+ },
+
+ Phases: {
+ Display: "display",
+ Edit: "edit",
+ },
+
+ getDefaultProps: function() {
+ return {
+ onValueChanged: function() {},
+ initalValue: '',
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ value: this.props.initalValue,
+ phase: this.Phases.Display,
+ }
+ },
+
+ getValue: function() {
+ return this.state.value;
+ },
+
+ setValue: function(val) {
+ this.setState({
+ value: val,
+ phase: this.Phases.Display,
+ });
+
+ this.onValueChanged();
+ },
+
+ cancelEdit: function() {
+ this.setState({
+ phase: this.Phases.Display,
+ });
+ },
+
+ onValueChanged: function() {
+ this.props.onValueChanged(this.state.value);
+ },
+};
diff --git a/src/controllers/atoms/create_room/CreateRoomButton.js b/src/controllers/atoms/create_room/CreateRoomButton.js
new file mode 100644
index 0000000000..f03dd56c97
--- /dev/null
+++ b/src/controllers/atoms/create_room/CreateRoomButton.js
@@ -0,0 +1,35 @@
+/*
+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');
+
+module.exports = {
+ propTypes: {
+ onCreateRoom: React.PropTypes.func,
+ },
+
+ getDefaultProps: function() {
+ return {
+ onCreateRoom: function() {},
+ };
+ },
+
+ onClick: function() {
+ this.props.onCreateRoom();
+ },
+};
diff --git a/src/controllers/atoms/create_room/Presets.js b/src/controllers/atoms/create_room/Presets.js
new file mode 100644
index 0000000000..5ff7327e5a
--- /dev/null
+++ b/src/controllers/atoms/create_room/Presets.js
@@ -0,0 +1,41 @@
+/*
+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');
+
+module.exports = {
+ propTypes: {
+ default_preset: React.PropTypes.string
+ },
+
+ getDefaultProps: function() {
+ return {
+ default_preset: 'private_chat',
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ preset: this.props.default_preset,
+ }
+ },
+
+ getPreset: function() {
+ return this.state.preset;
+ },
+};
diff --git a/src/controllers/atoms/create_room/RoomNameTextbox.js b/src/controllers/atoms/create_room/RoomNameTextbox.js
new file mode 100644
index 0000000000..e78692d992
--- /dev/null
+++ b/src/controllers/atoms/create_room/RoomNameTextbox.js
@@ -0,0 +1,41 @@
+/*
+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');
+
+module.exports = {
+ propTypes: {
+ default_name: React.PropTypes.string
+ },
+
+ getDefaultProps: function() {
+ return {
+ default_name: '',
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ room_name: this.props.default_name,
+ }
+ },
+
+ getName: function() {
+ return this.state.room_name;
+ },
+};
diff --git a/src/controllers/molecules/UserSelector.js b/src/controllers/molecules/UserSelector.js
new file mode 100644
index 0000000000..e7e0509690
--- /dev/null
+++ b/src/controllers/molecules/UserSelector.js
@@ -0,0 +1,57 @@
+/*
+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');
+
+module.exports = {
+ propTypes: {
+ initially_selected: React.PropTypes.arrayOf(React.PropTypes.string),
+ },
+
+ getDefaultProps: function() {
+ return {
+ initially_selected: [],
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ selected_users: this.props.initially_selected,
+ }
+ },
+
+ addUser: function(user_id) {
+ if (this.state.selected_users.indexOf(user_id == -1)) {
+ this.setState({
+ selected_users: this.state.selected_users.concat([user_id]),
+ });
+ }
+ },
+
+ removeUser: function(user_id) {
+ this.setState({
+ selected_users: this.state.selected_users.filter(function(e) {
+ return e != user_id;
+ }),
+ });
+ },
+
+ getUserIds: function() {
+ return this.state.selected_users;
+ }
+};
diff --git a/src/controllers/organisms/CreateRoom.js b/src/controllers/organisms/CreateRoom.js
new file mode 100644
index 0000000000..c2112ce58f
--- /dev/null
+++ b/src/controllers/organisms/CreateRoom.js
@@ -0,0 +1,92 @@
+/*
+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");
+
+module.exports = {
+ propTypes: {
+ onRoomCreated: React.PropTypes.func,
+ },
+
+ phases: {
+ CONFIG: "CONFIG", // We're waiting for user to configure and hit create.
+ CREATING: "CREATING", // We're sending the request.
+ CREATED: "CREATED", // We successfully created the room.
+ ERROR: "ERROR", // There was an error while trying to create room.
+ },
+
+ getDefaultProps: function() {
+ return {
+ onRoomCreated: function() {},
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ phase: this.phases.CONFIG,
+ error_string: "",
+ };
+ },
+
+ onCreateRoom: function() {
+ var options = {};
+
+ var room_name = this.getName();
+ if (room_name) {
+ options.name = room_name;
+ }
+
+ var preset = this.getPreset();
+ if (preset) {
+ options.preset = preset;
+ }
+
+ var invited_users = this.getInvitedUsers();
+ if (invited_users) {
+ options.invite = invited_users;
+ }
+
+ var cli = MatrixClientPeg.get();
+ if (!cli) {
+ // TODO: Error.
+ console.error("Cannot create room: No matrix client.");
+ return;
+ }
+
+ var deferred = MatrixClientPeg.get().createRoom(options);
+
+ this.setState({
+ phase: this.phases.CREATING,
+ });
+
+ var self = this;
+
+ deferred.then(function () {
+ self.setState({
+ phase: self.phases.CREATED,
+ });
+ self.props.onRoomCreated();
+ }, function(err) {
+ self.setState({
+ phase: self.phases.ERROR,
+ error_string: err.toString(),
+ });
+ });
+ }
+};