Split out render methods into 'views' leaving UI logic in 'controllers'. Hopefully should make it easier to skin / customise.
This commit is contained in:
parent
c8f0bac128
commit
2abea931ca
36 changed files with 303 additions and 184 deletions
9
src/controllers/atoms/LogoutButton.js
Normal file
9
src/controllers/atoms/LogoutButton.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
onClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'logout'
|
||||
});
|
||||
},
|
||||
};
|
3
src/controllers/atoms/MessageTimestamp.js
Normal file
3
src/controllers/atoms/MessageTimestamp.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
3
src/controllers/molecules/MEmoteTile.js
Normal file
3
src/controllers/molecules/MEmoteTile.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
3
src/controllers/molecules/MTextTile.js
Normal file
3
src/controllers/molecules/MTextTile.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
3
src/controllers/molecules/MatrixToolbar.js
Normal file
3
src/controllers/molecules/MatrixToolbar.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
45
src/controllers/molecules/MessageComposer.js
Normal file
45
src/controllers/molecules/MessageComposer.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
case 'focus_composer':
|
||||
this.refs.textarea.getDOMNode().focus();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onKeyDown: function (ev) {
|
||||
if (ev.keyCode == 13) {
|
||||
var contentText = this.refs.textarea.getDOMNode().value;
|
||||
|
||||
var content = null;
|
||||
if (/^\/me /i.test(contentText)) {
|
||||
content = {
|
||||
msgtype: 'm.emote',
|
||||
body: contentText.substring(4)
|
||||
};
|
||||
} else {
|
||||
content = {
|
||||
msgtype: 'm.text',
|
||||
body: contentText
|
||||
};
|
||||
}
|
||||
|
||||
MatrixClientPeg.get().sendMessage(this.props.roomId, content);
|
||||
this.refs.textarea.getDOMNode().value = '';
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
3
src/controllers/molecules/MessageTile.js
Normal file
3
src/controllers/molecules/MessageTile.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
6
src/controllers/molecules/ProgressBar.js
Normal file
6
src/controllers/molecules/ProgressBar.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
propTypes: {
|
||||
value: React.PropTypes.number,
|
||||
max: React.PropTypes.number
|
||||
},
|
||||
};
|
3
src/controllers/molecules/RoomHeader.js
Normal file
3
src/controllers/molecules/RoomHeader.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
10
src/controllers/molecules/RoomTile.js
Normal file
10
src/controllers/molecules/RoomTile.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
onClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: this.props.room.roomId
|
||||
});
|
||||
},
|
||||
};
|
3
src/controllers/molecules/SenderProfile.js
Normal file
3
src/controllers/molecules/SenderProfile.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
};
|
||||
|
44
src/controllers/molecules/ServerConfig.js
Normal file
44
src/controllers/molecules/ServerConfig.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
var React = require("react");
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
onHsUrlChanged: React.PropTypes.func,
|
||||
onIsUrlChanged: React.PropTypes.func,
|
||||
default_hs_url: React.PropTypes.string,
|
||||
default_is_url: React.PropTypes.string
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onHsUrlChanged: function() {},
|
||||
onIsUrlChanged: function() {},
|
||||
default_hs_url: 'https://matrix.org/',
|
||||
default_is_url: 'https://matrix.org/'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hs_url: this.props.default_hs_url,
|
||||
is_url: this.props.default_is_url,
|
||||
}
|
||||
},
|
||||
|
||||
hsChanged: function(ev) {
|
||||
this.setState({hs_url: ev.target.value});
|
||||
this.props.onHsUrlChanged(this.state.hs_url);
|
||||
},
|
||||
|
||||
isChanged: function(ev) {
|
||||
this.setState({is_url: ev.target.value});
|
||||
this.props.onIsUrlChanged(this.state.is_url);
|
||||
},
|
||||
|
||||
getHsUrl: function() {
|
||||
return this.state.hs_url;
|
||||
},
|
||||
|
||||
getIsUrl: function() {
|
||||
return this.state.is_url;
|
||||
},
|
||||
};
|
2
src/controllers/molecules/UnknownMessageTile.js
Normal file
2
src/controllers/molecules/UnknownMessageTile.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
module.exports = {
|
||||
};
|
59
src/controllers/organisms/RoomList.js
Normal file
59
src/controllers/organisms/RoomList.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
var React = require("react");
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
||||
var ComponentBroker = require('../../ComponentBroker');
|
||||
|
||||
var RoomTile = ComponentBroker.get("molecules/RoomTile");
|
||||
|
||||
module.exports = {
|
||||
componentWillMount: function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
cli.on("Room.timeline", this.onRoomTimeline);
|
||||
|
||||
this.setState({
|
||||
roomList: cli.getRooms(),
|
||||
activityMap: {}
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
this.state.activityMap[newProps.selectedRoom] = undefined;
|
||||
this.setState({
|
||||
activityMap: this.state.activityMap
|
||||
});
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
if (room.roomId == this.props.selectedRoom) return;
|
||||
if (ev.getSender() == MatrixClientPeg.get().credentials.userId) return;
|
||||
|
||||
// obviously this won't deep copy but we this shouldn't be necessary
|
||||
var amap = this.state.activityMap;
|
||||
amap[room.roomId] = 1;
|
||||
this.setState({
|
||||
roomMap: amap
|
||||
});
|
||||
},
|
||||
|
||||
makeRoomTiles: function() {
|
||||
var that = this;
|
||||
return this.state.roomList.map(function(room) {
|
||||
var selected = room.roomId == that.props.selectedRoom;
|
||||
return (
|
||||
<RoomTile
|
||||
room={room}
|
||||
key={room.roomId}
|
||||
selected={selected}
|
||||
unread={that.state.activityMap[room.roomId] === 1}
|
||||
/>
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
48
src/controllers/organisms/RoomView.js
Normal file
48
src/controllers/organisms/RoomView.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
this.atBottom = true;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
}
|
||||
},
|
||||
|
||||
// MatrixRoom still showing the messages from the old room?
|
||||
// Set the key to the room_id. Sadly you can no longer get at
|
||||
// the key from inside the component, or we'd check this in code.
|
||||
/*componentWillReceiveProps: function(props) {
|
||||
},*/
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
if (room.roomId != this.props.roomId) return;
|
||||
var messageUl = this.refs.messageList.getDOMNode();
|
||||
this.atBottom = messageUl.scrollHeight - messageUl.scrollTop <= messageUl.clientHeight;
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var messageUl = this.refs.messageList.getDOMNode();
|
||||
messageUl.scrollTop = messageUl.scrollHeight;
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.atBottom) {
|
||||
var messageUl = this.refs.messageList.getDOMNode();
|
||||
messageUl.scrollTop = messageUl.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
73
src/controllers/pages/MatrixChat.js
Normal file
73
src/controllers/pages/MatrixChat.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
// should be atomised
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var mxCliPeg = require("../../MatrixClientPeg");
|
||||
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
logged_in: !!(mxCliPeg.get() && mxCliPeg.get().credentials),
|
||||
ready: false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
if (this.state.logged_in) {
|
||||
this.startMatrixClient();
|
||||
}
|
||||
this.focusComposer = false;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.focusComposer) {
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
this.focusComposer = false;
|
||||
}
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
case 'logout':
|
||||
this.setState({
|
||||
logged_in: false,
|
||||
ready: false
|
||||
});
|
||||
mxCliPeg.get().removeAllListeners();
|
||||
mxCliPeg.replace(null);
|
||||
break;
|
||||
case 'view_room':
|
||||
this.setState({
|
||||
currentRoom: payload.room_id
|
||||
});
|
||||
this.focusComposer = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onLoggedIn: function() {
|
||||
this.setState({logged_in: true});
|
||||
this.startMatrixClient();
|
||||
},
|
||||
|
||||
startMatrixClient: function() {
|
||||
var cli = mxCliPeg.get();
|
||||
var that = this;
|
||||
cli.on('syncComplete', function() {
|
||||
var firstRoom = null;
|
||||
if (cli.getRooms() && cli.getRooms().length) {
|
||||
firstRoom = cli.getRooms()[0].roomId;
|
||||
}
|
||||
that.setState({ready: true, currentRoom: firstRoom});
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
});
|
||||
cli.startClient();
|
||||
},
|
||||
};
|
||||
|
101
src/controllers/templates/Login.js
Normal file
101
src/controllers/templates/Login.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
|
||||
var ComponentBroker = require("../../ComponentBroker");
|
||||
|
||||
var ServerConfig = ComponentBroker.get("molecules/ServerConfig");
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
step: 'choose_hs',
|
||||
busy: false,
|
||||
currentStep: 0,
|
||||
totalSteps: 1
|
||||
};
|
||||
},
|
||||
|
||||
setStep: function(step) {
|
||||
this.setState({ step: step, errorText: '', busy: false });
|
||||
},
|
||||
|
||||
onHSChosen: function(ev) {
|
||||
ev.preventDefault();
|
||||
MatrixClientPeg.replaceUsingUrl(this.refs.serverConfig.getHsUrl());
|
||||
this.setState({hs_url: this.refs.serverConfig.getHsUrl()});
|
||||
this.setStep("fetch_stages");
|
||||
var cli = MatrixClientPeg.get();
|
||||
this.setState({busy: true});
|
||||
var that = this;
|
||||
cli.loginFlows().then(function(result) {
|
||||
that.setState({
|
||||
flows: result.flows,
|
||||
currentStep: 1,
|
||||
totalSteps: result.flows.length+1
|
||||
});
|
||||
that.setStep('stage_'+result.flows[0].type);
|
||||
}, function(error) {
|
||||
that.setStep("choose_hs");
|
||||
that.setState({errorText: 'Unable to contact the given Home Server'});
|
||||
});
|
||||
},
|
||||
|
||||
onUserPassEntered: function(ev) {
|
||||
ev.preventDefault();
|
||||
this.setState({busy: true});
|
||||
var that = this;
|
||||
MatrixClientPeg.get().login('m.login.password', {
|
||||
'user': that.refs.user.getDOMNode().value,
|
||||
'password': that.refs.pass.getDOMNode().value
|
||||
}).then(function(data) {
|
||||
// XXX: we assume this means we're logged in, but there could be a next stage
|
||||
MatrixClientPeg.replace(Matrix.createClient({
|
||||
baseUrl: that.state.hs_url,
|
||||
userId: data.user_id,
|
||||
accessToken: data.access_token
|
||||
}));
|
||||
var localStorage = window.localStorage;
|
||||
if (localStorage) {
|
||||
localStorage.setItem("mx_hs_url", that.state.hs_url);
|
||||
localStorage.setItem("mx_user_id", data.user_id);
|
||||
localStorage.setItem("mx_access_token", data.access_token);
|
||||
} else {
|
||||
console.warn("No local storage available: can't persist session!");
|
||||
}
|
||||
if (that.props.onLoggedIn) {
|
||||
that.props.onLoggedIn();
|
||||
}
|
||||
/*dis.dispatch({
|
||||
'action': 'logged_in'
|
||||
});*/
|
||||
}, function(error) {
|
||||
that.setStep("stage_m.login.password");
|
||||
that.setState({errorText: 'Login failed.'});
|
||||
});
|
||||
},
|
||||
|
||||
componentForStep: function(step) {
|
||||
switch (step) {
|
||||
case 'choose_hs':
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onHSChosen}>
|
||||
<ServerConfig ref="serverConfig" />
|
||||
<input type="submit" value="Continue" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
// XXX: clearly these should be separate organisms
|
||||
case 'stage_m.login.password':
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onUserPassEntered}>
|
||||
<input ref="user" type="text" placeholder="username" /><br />
|
||||
<input ref="pass" type="password" placeholder="password" /><br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue