Merge branch 'vector' into irc-style-commands

This commit is contained in:
Kegan Dougal 2015-07-16 17:41:30 +01:00
commit 91943d8a45
16 changed files with 323 additions and 118 deletions

View file

@ -25,14 +25,15 @@ module.exports = React.createClass({
mixins: [PresetsController], mixins: [PresetsController],
onValueChanged: function(ev) { onValueChanged: function(ev) {
this.setState({preset: ev.target.value}) this.props.onChange(ev.target.value)
}, },
render: function() { render: function() {
return ( return (
<select className="mx_Presets" onChange={this.onValueChanged} defaultValue={this.state.preset}> <select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
<option value="private_chat">Private Chat</option> <option value={this.Presets.PrivateChat}>Private Chat</option>
<option value="public_chat">Public Chat</option> <option value={this.Presets.PublicChat}>Public Chat</option>
<option value={this.Presets.Custom}>Custom</option>
</select> </select>
); );
} }

View file

@ -0,0 +1,79 @@
/*
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 RoomAliasController = require("../../../../../src/controllers/atoms/create_room/RoomAlias");
module.exports = React.createClass({
displayName: 'RoomAlias',
mixins: [RoomAliasController],
onValueChanged: function(ev) {
this.props.onChange(ev.target.value);
},
onFocus: function(ev) {
var target = ev.target;
var curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "") {
setTimeout(function() {
target.value = "#:" + this.props.homeserver;
target.setSelectionRange(1, 1);
}, 0);
} else {
var suffix = ":" + this.props.homeserver;
setTimeout(function() {
target.setSelectionRange(
curr_val.startsWith("#") ? 1 : 0,
curr_val.endsWith(suffix) ? (target.value.length - suffix.length) : target.value.length
);
}, 0);
}
}
},
onBlur: function(ev) {
var curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "#:" + this.props.homeserver) {
ev.target.value = "";
return;
}
if (curr_val != "") {
var new_val = ev.target.value;
var suffix = ":" + this.props.homeserver;
if (!curr_val.startsWith("#")) new_val = "#" + new_val;
if (!curr_val.endsWith(suffix)) new_val = new_val + suffix;
ev.target.value = new_val;
}
}
},
render: function() {
return (
<input type="text" className="mx_RoomAlias" placeholder="Alias (optional)"
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
value={this.props.alias}/>
);
}
});

View file

@ -1,36 +0,0 @@
/*
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 (
<input type="text" className="mx_RoomNameTextbox" placeholder="ex. MyNewRoom" onChange={this.onValueChanged}/>
);
}
});

View file

@ -33,11 +33,15 @@ module.exports = React.createClass({
dis.dispatch({action: 'view_user_settings'}); dis.dispatch({action: 'view_user_settings'});
}, },
onCreateRoomClick: function() {
dis.dispatch({action: 'view_create_room'});
},
render: function() { render: function() {
return ( return (
<div className="mx_DirectoryMenu"> <div className="mx_DirectoryMenu">
<div className="mx_DirectoryMenu_options"> <div className="mx_DirectoryMenu_options">
<div className="mx_RoomTile"> <div className="mx_RoomTile" onClick={this.onCreateRoomClick}>
<div className="mx_RoomTile_avatar"> <div className="mx_RoomTile_avatar">
<img src="img/create-big.png" width="42" height="42"/> <img src="img/create-big.png" width="42" height="42"/>
</div> </div>

View file

@ -32,13 +32,14 @@ module.exports = React.createClass({
var ev = this.props.mxEvent; var ev = this.props.mxEvent;
// XXX: SYJS-16 // XXX: SYJS-16
var senderName = ev.sender ? ev.sender.name : "Someone"; var senderName = ev.sender ? ev.sender.name : "Someone";
var targetName = ev.target ? ev.target.name : "Someone";
switch (ev.getContent().membership) { switch (ev.getContent().membership) {
case 'invite': case 'invite':
return senderName + " invited " + ev.target.name + "."; return senderName + " invited " + targetName + ".";
case 'join': case 'join':
return senderName + " joined the room."; return targetName + " joined the room.";
case 'leave': case 'leave':
return senderName + " left the room."; return targetName + " left the room.";
} }
}, },
@ -47,7 +48,7 @@ module.exports = React.createClass({
return ( return (
<div className="mx_MessageTile"> <div className="mx_MessageTile">
<div className="mx_MessageTile_avatar"> <div className="mx_MessageTile_avatar">
<img src={ this.props.mxEvent.sender ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.sender, 40, 40, "crop") : null } width="40" height="40"/> <img src={ this.props.mxEvent.target ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.target, 40, 40, "crop") : null } width="40" height="40"/>
</div> </div>
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> <MessageTimestamp ts={this.props.mxEvent.getTs()} />
<span className="mx_SenderProfile"></span> <span className="mx_SenderProfile"></span>

View file

@ -26,17 +26,19 @@ module.exports = React.createClass({
onAddUserId: function() { onAddUserId: function() {
this.addUser(this.refs.user_id_input.getDOMNode().value); this.addUser(this.refs.user_id_input.getDOMNode().value);
this.refs.user_id_input.getDOMNode().value = "";
}, },
render: function() { render: function() {
var self = this;
return ( return (
<div> <div>
<ul className="mx_UserSelector_UserIdList" ref="list"> <ul className="mx_UserSelector_UserIdList" ref="list">
{this.state.selected_users.map(function(user_id, i) { {this.props.selected_users.map(function(user_id, i) {
return <li key={user_id}>{user_id}</li> return <li key={user_id}>{user_id} - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>
})} })}
</ul> </ul>
<input type="text" ref="user_id_input" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/> <input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/>
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">Add User</button> <button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">Add User</button>
</div> </div>
); );

View file

@ -22,11 +22,17 @@ var CreateRoomController = require("../../../../src/controllers/organisms/Create
var ComponentBroker = require('../../../../src/ComponentBroker'); var ComponentBroker = require('../../../../src/ComponentBroker');
var PresetValues = require('../../../../src/controllers/atoms/create_room/Presets').Presets;
var CreateRoomButton = ComponentBroker.get("atoms/create_room/CreateRoomButton"); var CreateRoomButton = ComponentBroker.get("atoms/create_room/CreateRoomButton");
var RoomNameTextbox = ComponentBroker.get("atoms/create_room/RoomNameTextbox"); var RoomNameTextbox = ComponentBroker.get("atoms/create_room/RoomNameTextbox");
var RoomTopic = ComponentBroker.get("atoms/create_room/RoomTopic");
var RoomAlias = ComponentBroker.get("atoms/create_room/RoomAlias");
var Presets = ComponentBroker.get("atoms/create_room/Presets"); var Presets = ComponentBroker.get("atoms/create_room/Presets");
var UserSelector = ComponentBroker.get("molecules/UserSelector"); var UserSelector = ComponentBroker.get("molecules/UserSelector");
var Loader = require("react-loader");
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'CreateRoom', displayName: 'CreateRoom',
@ -40,15 +46,85 @@ module.exports = React.createClass({
return this.refs.name_textbox.getName(); return this.refs.name_textbox.getName();
}, },
getTopic: function() {
return this.refs.topic.getTopic();
},
getAliasLocalpart: function() {
return this.refs.alias.getAliasLocalpart();
},
getInvitedUsers: function() { getInvitedUsers: function() {
return this.refs.user_selector.getUserIds(); return this.refs.user_selector.getUserIds();
}, },
onPresetChanged: function(preset) {
switch (preset) {
case PresetValues.PrivateChat:
this.setState({
preset: preset,
is_private: true,
share_history: false,
});
break;
case PresetValues.PublicChat:
this.setState({
preset: preset,
is_private: false,
share_history: true,
});
break;
case PresetValues.Custom:
this.setState({
preset: preset,
});
break;
}
},
onPrivateChanged: function(ev) {
this.setState({
preset: PresetValues.Custom,
is_private: ev.target.checked,
});
},
onShareHistoryChanged: function(ev) {
this.setState({
preset: PresetValues.Custom,
share_history: ev.target.checked,
});
},
onTopicChange: function(ev) {
this.setState({
topic: ev.target.value,
});
},
onNameChange: function(ev) {
this.setState({
room_name: ev.target.value,
});
},
onInviteChanged: function(invited_users) {
this.setState({
invited_users: invited_users,
});
},
onAliasChanged: function(alias) {
this.setState({
alias: alias
})
},
render: function() { render: function() {
var curr_phase = this.state.phase; var curr_phase = this.state.phase;
if (curr_phase == this.phases.CREATING) { if (curr_phase == this.phases.CREATING) {
return ( return (
<div>Creating...</div> <Loader/>
); );
} else { } else {
var error_box = ""; var error_box = "";
@ -61,10 +137,15 @@ module.exports = React.createClass({
} }
return ( return (
<div className="mx_CreateRoom"> <div className="mx_CreateRoom">
<label>Room Name <RoomNameTextbox ref="name_textbox" /></label> <input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <br />
<Presets ref="presets"/> <textarea ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder="Description"/> <br />
<UserSelector ref="user_selector"/> <RoomAlias ref="alias" alias={this.state.alias} onChange={this.onAliasChanged}/> <br />
<CreateRoomButton onCreateRoom={this.onCreateRoom} /> <UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged}/> <br />
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
<label><input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/> Make this room private</label>
<label><input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/> Share message history with new users</label>
<label><input type="checkbox"/> Encrypt room</label>
<CreateRoomButton onCreateRoom={this.onCreateRoom} /> <br />
{error_box} {error_box}
</div> </div>
); );

View file

@ -25,28 +25,44 @@ var RightPanel = ComponentBroker.get('organisms/RightPanel');
var Login = ComponentBroker.get('templates/Login'); var Login = ComponentBroker.get('templates/Login');
var UserSettings = ComponentBroker.get('organisms/UserSettings'); var UserSettings = ComponentBroker.get('organisms/UserSettings');
var Register = ComponentBroker.get('templates/Register'); var Register = ComponentBroker.get('templates/Register');
var CreateRoom = ComponentBroker.get('organisms/CreateRoom');
var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat"); var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat");
// should be atomised // should be atomised
var Loader = require("react-loader"); var Loader = require("react-loader");
var dis = require("../../../../src/dispatcher");
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MatrixChat', displayName: 'MatrixChat',
mixins: [MatrixChatController], mixins: [MatrixChatController],
onRoomCreated: function(room_id) {
dis.dispatch({
action: "view_room",
room_id: room_id,
});
},
render: function() { render: function() {
if (this.state.logged_in && this.state.ready) { if (this.state.logged_in && this.state.ready) {
var page_element; var page_element;
var right_panel = ""; var right_panel = "";
if (this.state.page_type == this.PageTypes.RoomView) { switch (this.state.page_type) {
case this.PageTypes.RoomView:
page_element = <RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} /> page_element = <RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} />
right_panel = <RightPanel roomId={this.state.currentRoom} /> right_panel = <RightPanel roomId={this.state.currentRoom} />
} else if (this.state.page_type == this.PageTypes.UserSettings) { break;
case this.PageTypes.UserSettings:
page_element = <UserSettings /> page_element = <UserSettings />
break;
case this.PageTypes.CreateRoom:
page_element = <CreateRoom onRoomCreated={this.onRoomCreated}/>
break;
} }
return ( return (

View file

@ -31,6 +31,24 @@ module.exports = React.createClass({
displayName: 'Login', displayName: 'Login',
mixins: [LoginController], mixins: [LoginController],
getHsUrl: function() {
return this.refs.serverConfig.getHsUrl();
},
getIsUrl: function() {
return this.refs.serverConfig.getIsUrl();
},
/**
* Gets the form field values for the current login stage
*/
getFormVals: function() {
return {
'username': this.refs.user.getDOMNode().value,
'password': this.refs.pass.getDOMNode().value
};
},
componentForStep: function(step) { componentForStep: function(step) {
switch (step) { switch (step) {
case 'choose_hs': case 'choose_hs':

View file

@ -63,6 +63,8 @@ require('../skins/base/views/atoms/EnableNotificationsButton');
require('../skins/base/views/atoms/MessageTimestamp'); require('../skins/base/views/atoms/MessageTimestamp');
require('../skins/base/views/atoms/create_room/CreateRoomButton'); require('../skins/base/views/atoms/create_room/CreateRoomButton');
require('../skins/base/views/atoms/create_room/RoomNameTextbox'); require('../skins/base/views/atoms/create_room/RoomNameTextbox');
require('../skins/base/views/atoms/create_room/RoomTopic');
require('../skins/base/views/atoms/create_room/RoomAlias');
require('../skins/base/views/atoms/create_room/Presets'); require('../skins/base/views/atoms/create_room/Presets');
require('../skins/base/views/atoms/EditableText'); require('../skins/base/views/atoms/EditableText');
require('../skins/base/views/molecules/MatrixToolbar'); require('../skins/base/views/molecules/MatrixToolbar');

View file

@ -18,24 +18,23 @@ limitations under the License.
var React = require('react'); var React = require('react');
var Presets = {
PrivateChat: "private_chat",
PublicChat: "public_chat",
Custom: "custom",
};
module.exports = { module.exports = {
propTypes: { propTypes: {
default_preset: React.PropTypes.string onChange: React.PropTypes.func,
preset: React.PropTypes.string
}, },
Presets: Presets,
getDefaultProps: function() { getDefaultProps: function() {
return { return {
default_preset: 'private_chat', onChange: function() {},
}; };
}, },
getInitialState: function() {
return {
preset: this.props.default_preset,
}
},
getPreset: function() {
return this.state.preset;
},
}; };

View file

@ -20,22 +20,30 @@ var React = require('react');
module.exports = { module.exports = {
propTypes: { propTypes: {
default_name: React.PropTypes.string // Specifying a homeserver will make magical things happen when you,
// e.g. start typing in the room alias box.
homeserver: React.PropTypes.string,
alias: React.PropTypes.string,
onChange: React.PropTypes.func,
}, },
getDefaultProps: function() { getDefaultProps: function() {
return { return {
default_name: '', onChange: function() {},
alias: '',
}; };
}, },
getInitialState: function() { getAliasLocalpart: function() {
return { var room_alias = this.props.alias;
room_name: this.props.default_name,
if (room_alias && this.props.homeserver) {
var suffix = ":" + this.props.homeserver;
if (room_alias.startsWith("#") && room_alias.endsWith(suffix)) {
room_alias = room_alias.slice(1, -suffix.length);
}
} }
},
getName: function() { return room_alias;
return this.state.room_name;
}, },
}; };

View file

@ -20,38 +20,26 @@ var React = require('react');
module.exports = { module.exports = {
propTypes: { propTypes: {
initially_selected: React.PropTypes.arrayOf(React.PropTypes.string), onChange: React.PropTypes.func,
selected_users: React.PropTypes.arrayOf(React.PropTypes.string),
}, },
getDefaultProps: function() { getDefaultProps: function() {
return { return {
initially_selected: [], onChange: function() {},
selected: [],
}; };
}, },
getInitialState: function() {
return {
selected_users: this.props.initially_selected,
}
},
addUser: function(user_id) { addUser: function(user_id) {
if (this.state.selected_users.indexOf(user_id == -1)) { if (this.props.selected_users.indexOf(user_id == -1)) {
this.setState({ this.props.onChange(this.props.selected_users.concat([user_id]));
selected_users: this.state.selected_users.concat([user_id]),
});
} }
}, },
removeUser: function(user_id) { removeUser: function(user_id) {
this.setState({ this.props.onChange(this.props.selected_users.filter(function(e) {
selected_users: this.state.selected_users.filter(function(e) {
return e != user_id; return e != user_id;
}), }));
});
}, },
getUserIds: function() {
return this.state.selected_users;
}
}; };

View file

@ -18,6 +18,7 @@ limitations under the License.
var React = require("react"); var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg"); var MatrixClientPeg = require("../../MatrixClientPeg");
var PresetValues = require('../atoms/create_room/Presets').Presets;
module.exports = { module.exports = {
propTypes: { propTypes: {
@ -41,25 +42,52 @@ module.exports = {
return { return {
phase: this.phases.CONFIG, phase: this.phases.CONFIG,
error_string: "", error_string: "",
is_private: true,
share_history: false,
default_preset: PresetValues.PrivateChat,
topic: '',
room_name: '',
invited_users: [],
}; };
}, },
onCreateRoom: function() { onCreateRoom: function() {
var options = {}; var options = {};
var room_name = this.getName(); if (this.state.room_name) {
if (room_name) { options.name = this.state.room_name;
options.name = room_name;
} }
var preset = this.getPreset(); if (this.state.topic) {
if (preset) { options.topic = this.state.topic;
options.preset = preset;
} }
var invited_users = this.getInvitedUsers(); if (this.state.preset) {
if (invited_users) { if (this.state.preset != PresetValues.Custom) {
options.invite = invited_users; options.preset = this.state.preset;
} else {
options.initial_state = [
{
type: "m.room.join_rules",
content: {
"join_rules": this.state.is_private ? "invite" : "public"
}
},
{
type: "m.room.history_visibility",
content: {
"history_visibility": this.state.share_history ? "shared" : "invited"
}
},
];
}
}
options.invite = this.state.invited_users;
var alias = this.getAliasLocalpart();
if (alias) {
options.room_alias_name = alias;
} }
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
@ -77,11 +105,11 @@ module.exports = {
var self = this; var self = this;
deferred.then(function () { deferred.then(function (resp) {
self.setState({ self.setState({
phase: self.phases.CREATED, phase: self.phases.CREATED,
}); });
self.props.onRoomCreated(); self.props.onRoomCreated(resp.room_id);
}, function(err) { }, function(err) {
self.setState({ self.setState({
phase: self.phases.ERROR, phase: self.phases.ERROR,

View file

@ -32,6 +32,11 @@ module.exports = {
PageTypes: { PageTypes: {
RoomView: "room_view", RoomView: "room_view",
UserSettings: "user_settings", UserSettings: "user_settings",
CreateRoom: "create_room",
},
AuxPanel: {
RoomSettings: "room_settings",
}, },
getInitialState: function() { getInitialState: function() {
@ -39,6 +44,7 @@ module.exports = {
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials), logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
ready: false, ready: false,
page_type: this.PageTypes.RoomView, page_type: this.PageTypes.RoomView,
aux_panel: null,
}; };
}, },
@ -143,6 +149,11 @@ module.exports = {
page_type: this.PageTypes.UserSettings, page_type: this.PageTypes.UserSettings,
}); });
break; break;
case 'view_create_room':
this.setState({
page_type: this.PageTypes.CreateRoom,
});
break;
} }
}, },

View file

@ -41,18 +41,18 @@ module.exports = {
onHSChosen: function(ev) { onHSChosen: function(ev) {
ev.preventDefault(); ev.preventDefault();
MatrixClientPeg.replaceUsingUrls( MatrixClientPeg.replaceUsingUrls(
this.refs.serverConfig.getHsUrl(), this.getHsUrl(),
this.refs.serverConfig.getIsUrl() this.getIsUrl()
); );
this.setState({ this.setState({
hs_url: this.refs.serverConfig.getHsUrl(), hs_url: this.getHsUrl(),
is_url: this.refs.serverConfig.getIsUrl() is_url: this.getIsUrl()
}); });
this.setStep("fetch_stages"); this.setStep("fetch_stages");
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
this.setState({busy: true}); this.setState({busy: true});
var that = this; var that = this;
cli.loginFlows().then(function(result) { cli.loginFlows().done(function(result) {
that.setState({ that.setState({
flows: result.flows, flows: result.flows,
currentStep: 1, currentStep: 1,
@ -69,9 +69,12 @@ module.exports = {
ev.preventDefault(); ev.preventDefault();
this.setState({busy: true}); this.setState({busy: true});
var that = this; var that = this;
var formVals = this.getFormVals();
MatrixClientPeg.get().login('m.login.password', { MatrixClientPeg.get().login('m.login.password', {
'user': that.refs.user.getDOMNode().value, 'user': formVals.username,
'password': that.refs.pass.getDOMNode().value 'password': formVals.password
}).done(function(data) { }).done(function(data) {
// XXX: we assume this means we're logged in, but there could be a next stage // XXX: we assume this means we're logged in, but there could be a next stage
MatrixClientPeg.replace(Matrix.createClient({ MatrixClientPeg.replace(Matrix.createClient({