diff --git a/package.json b/package.json
index ff93588b..d71fdac5 100644
--- a/package.json
+++ b/package.json
@@ -31,10 +31,15 @@
"modernizr": "^3.1.0",
"matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop",
"matrix-react-sdk": "^0.0.2",
+ "modernizr": "^3.1.0",
"q": "^1.4.1",
- "react": "^0.13.3",
+ "react": "^0.14.2",
+ "react-dnd": "^2.0.2",
+ "react-dnd-html5-backend": "^2.0.0",
+ "react-dom": "^0.14.2",
+ "react-gemini-scrollbar": "^2.0.1",
"react-loader": "^1.4.0",
- "sanitize-html": "^1.11.1"
+ "sanitize-html": "^1.0.0"
},
"devDependencies": {
"babel": "^5.8.23",
diff --git a/src/ContextualMenu.js b/src/ContextualMenu.js
index 7865e45a..3327aa94 100644
--- a/src/ContextualMenu.js
+++ b/src/ContextualMenu.js
@@ -18,6 +18,7 @@ limitations under the License.
'use strict';
var React = require('react');
+var ReactDOM = require('react-dom');
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
@@ -74,7 +75,7 @@ module.exports = {
);
- React.render(menu, this.getOrCreateContainer());
+ ReactDOM.render(menu, this.getOrCreateContainer());
return {close: closeMenu};
},
diff --git a/src/Resend.js b/src/Resend.js
new file mode 100644
index 00000000..52b7c936
--- /dev/null
+++ b/src/Resend.js
@@ -0,0 +1,24 @@
+var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
+var dis = require('matrix-react-sdk/lib/dispatcher');
+
+module.exports = {
+ resend: function(event) {
+ MatrixClientPeg.get().resendEvent(
+ event, MatrixClientPeg.get().getRoom(event.getRoomId())
+ ).done(function() {
+ dis.dispatch({
+ action: 'message_sent',
+ event: event
+ });
+ }, function() {
+ dis.dispatch({
+ action: 'message_send_failed',
+ event: event
+ });
+ });
+ dis.dispatch({
+ action: 'message_resend_started',
+ event: event
+ });
+ },
+};
\ No newline at end of file
diff --git a/src/controllers/organisms/RoomList.js b/src/controllers/organisms/RoomList.js
index 964a2648..37d4a4e4 100644
--- a/src/controllers/organisms/RoomList.js
+++ b/src/controllers/organisms/RoomList.js
@@ -17,22 +17,31 @@ limitations under the License.
'use strict';
var React = require("react");
+var ReactDOM = require("react-dom");
+
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var RoomListSorter = require("matrix-react-sdk/lib/RoomListSorter");
var dis = require("matrix-react-sdk/lib/dispatcher");
var sdk = require('matrix-react-sdk');
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
-var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var HIDE_CONFERENCE_CHANS = true;
module.exports = {
+ getInitialState: function() {
+ return {
+ activityMap: null,
+ lists: {},
+ }
+ },
+
componentWillMount: function() {
var cli = MatrixClientPeg.get();
cli.on("Room", this.onRoom);
cli.on("Room.timeline", this.onRoomTimeline);
cli.on("Room.name", this.onRoomName);
+ cli.on("Room.tags", this.onRoomTags);
cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomMember.name", this.onRoomMemberName);
@@ -47,11 +56,6 @@ module.exports = {
onAction: function(payload) {
switch (payload.action) {
- // listen for call state changes to prod the render method, which
- // may hide the global CallView if the call it is tracking is dead
- case 'call_state':
- this._recheckCallElement(this.props.selectedRoom);
- break;
case 'view_tooltip':
this.tooltip = payload.tooltip;
this._repositionTooltip();
@@ -72,7 +76,6 @@ module.exports = {
componentWillReceiveProps: function(newProps) {
this.state.activityMap[newProps.selectedRoom] = undefined;
- this._recheckCallElement(newProps.selectedRoom);
this.setState({
activityMap: this.state.activityMap
});
@@ -109,6 +112,10 @@ module.exports = {
this.refreshRoomList();
},
+ onRoomTags: function(event, room) {
+ this.refreshRoomList();
+ },
+
onRoomStateEvents: function(ev, state) {
setTimeout(this.refreshRoomList, 0);
},
@@ -117,26 +124,36 @@ module.exports = {
setTimeout(this.refreshRoomList, 0);
},
-
refreshRoomList: function() {
+ // TODO: rather than bluntly regenerating and re-sorting everything
+ // every time we see any kind of room change from the JS SDK
+ // we could do incremental updates on our copy of the state
+ // based on the room which has actually changed. This would stop
+ // us re-rendering all the sublists every time anything changes anywhere
+ // in the state of the client.
this.setState(this.getRoomLists());
},
getRoomLists: function() {
- var s = {};
- var inviteList = [];
- s.roomList = RoomListSorter.mostRecentActivityFirst(
- MatrixClientPeg.get().getRooms().filter(function(room) {
- var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+ var s = { lists: {} };
- if (me && me.membership == "invite") {
- inviteList.push(room);
- return false;
- }
+ s.lists["m.invite"] = [];
+ s.lists["m.favourite"] = [];
+ s.lists["m.recent"] = [];
+ s.lists["m.lowpriority"] = [];
+ s.lists["m.archived"] = [];
+ MatrixClientPeg.get().getRooms().forEach(function(room) {
+ var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+
+ if (me && me.membership == "invite") {
+ s.lists["m.invite"].push(room);
+ }
+ else {
var shouldShowRoom = (
me && (me.membership == "join")
);
+
// hiding conf rooms only ever toggles shouldShowRoom to false
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
// we want to hide the 1:1 conf<->user room and not the group chat
@@ -151,48 +168,34 @@ module.exports = {
}
}
}
- return shouldShowRoom;
- })
- );
- s.inviteList = RoomListSorter.mostRecentActivityFirst(inviteList);
- return s;
- },
- _recheckCallElement: function(selectedRoomId) {
- // if we aren't viewing a room with an ongoing call, but there is an
- // active call, show the call element - we need to do this to make
- // audio/video not crap out
- var activeCall = CallHandler.getAnyActiveCall();
- var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
- var showCall = (activeCall && !callForRoom);
- this.setState({
- show_call_element: showCall
+ if (shouldShowRoom) {
+ var tagNames = Object.keys(room.tags);
+ if (tagNames.length) {
+ for (var i = 0; i < tagNames.length; i++) {
+ var tagName = tagNames[i];
+ s.lists[tagName] = s.lists[tagName] || [];
+ s.lists[tagNames[i]].push(room);
+ }
+ }
+ else {
+ s.lists["m.recent"].push(room);
+ }
+ }
+ }
});
+
+ //console.log("calculated new roomLists; m.recent = " + s.lists["m.recent"]);
+
+ // we actually apply the sorting to this when receiving the prop in RoomSubLists.
+
+ return s;
},
_repositionTooltip: function(e) {
if (this.tooltip && this.tooltip.parentElement) {
- var scroll = this.getDOMNode();
- this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px";
+ var scroll = ReactDOM.findDOMNode(this);
+ this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.children[2].scrollTop) + "px";
}
},
-
- makeRoomTiles: function(list, isInvite) {
- var self = this;
- var RoomTile = sdk.getComponent("molecules.RoomTile");
- return list.map(function(room) {
- var selected = room.roomId == self.props.selectedRoom;
- return (
-
- );
- });
- }
};
diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js
index c305a9c9..d8832fa3 100644
--- a/src/controllers/organisms/RoomView.js
+++ b/src/controllers/organisms/RoomView.js
@@ -17,6 +17,7 @@ limitations under the License.
var Matrix = require("matrix-js-sdk");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var React = require("react");
+var ReactDOM = require("react-dom");
var q = require("q");
var ContentMessages = require("matrix-react-sdk/lib//ContentMessages");
var WhoIsTyping = require("matrix-react-sdk/lib/WhoIsTyping");
@@ -24,6 +25,7 @@ var Modal = require("matrix-react-sdk/lib/Modal");
var sdk = require('matrix-react-sdk/lib/index');
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
var VectorConferenceHandler = require('../../modules/VectorConferenceHandler');
+var Resend = require("../../Resend");
var dis = require("matrix-react-sdk/lib/dispatcher");
@@ -32,8 +34,9 @@ var INITIAL_SIZE = 20;
module.exports = {
getInitialState: function() {
+ var room = this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null;
return {
- room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
+ room: room,
messageCap: INITIAL_SIZE,
editingRoomSettings: false,
uploadingRoomSettings: false,
@@ -41,6 +44,8 @@ module.exports = {
draggingFile: false,
searching: false,
searchResults: null,
+ syncState: MatrixClientPeg.get().getSyncState(),
+ hasUnsentMessages: this._hasUnsentMessages(room)
}
},
@@ -51,12 +56,13 @@ module.exports = {
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
+ MatrixClientPeg.get().on("sync", this.onSyncStateChange);
this.atBottom = true;
},
componentWillUnmount: function() {
if (this.refs.messageWrapper) {
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
+ var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper);
messageWrapper.removeEventListener('drop', this.onDrop);
messageWrapper.removeEventListener('dragover', this.onDragOver);
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
@@ -69,6 +75,7 @@ module.exports = {
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
+ MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
}
},
@@ -76,6 +83,9 @@ module.exports = {
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
+ this.setState({
+ hasUnsentMessages: this._hasUnsentMessages(this.state.room)
+ });
case 'message_resend_started':
this.setState({
room: MatrixClientPeg.get().getRoom(this.props.roomId)
@@ -92,8 +102,8 @@ module.exports = {
// scroll to bottom
var messageWrapper = this.refs.messageWrapper;
if (messageWrapper) {
- messageWrapper = messageWrapper.getDOMNode();
- messageWrapper.scrollTop = messageWrapper.scrollHeight;
+ var messageWrapperScroll = ReactDOM.findDOMNode(messageWrapper).children[2];
+ messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
}
}
@@ -107,6 +117,12 @@ module.exports = {
}
},
+ onSyncStateChange: function(state) {
+ this.setState({
+ syncState: state
+ });
+ },
+
// 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.
@@ -116,7 +132,7 @@ module.exports = {
onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (!this.isMounted()) return;
- // ignore anything that comes in whilst pagingating: we get one
+ // ignore anything that comes in whilst paginating: we get one
// event for each new matrix event so this would cause a huge
// number of UI updates. Just update the UI when the paginate
// call returns.
@@ -128,10 +144,10 @@ module.exports = {
if (room.roomId != this.props.roomId) return;
if (this.refs.messageWrapper) {
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
+ var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
this.atBottom = (
- messageWrapper.scrollHeight - messageWrapper.scrollTop <=
- (messageWrapper.clientHeight + 150)
+ messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <=
+ (messageWrapperScroll.clientHeight + 150)
);
}
@@ -184,6 +200,19 @@ module.exports = {
this._updateConfCallNotification();
},
+ _hasUnsentMessages: function(room) {
+ return this._getUnsentMessages(room).length > 0;
+ },
+
+ _getUnsentMessages: function(room) {
+ if (!room) { return []; }
+ // TODO: It would be nice if the JS SDK provided nicer constant-time
+ // constructs rather than O(N) (N=num msgs) on this.
+ return room.timeline.filter(function(ev) {
+ return ev.status === Matrix.EventStatus.NOT_SENT;
+ });
+ },
+
_updateConfCallNotification: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) return;
@@ -208,14 +237,16 @@ module.exports = {
componentDidMount: function() {
if (this.refs.messageWrapper) {
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
+ var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper);
messageWrapper.addEventListener('drop', this.onDrop);
messageWrapper.addEventListener('dragover', this.onDragOver);
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
- messageWrapper.scrollTop = messageWrapper.scrollHeight;
+ var messageWrapperScroll = messageWrapper.children[2];
+
+ messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
this.sendReadReceipt();
@@ -228,17 +259,17 @@ module.exports = {
componentDidUpdate: function() {
if (!this.refs.messageWrapper) return;
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
+ var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
if (this.state.paginating && !this.waiting_for_paginate) {
- var heightGained = messageWrapper.scrollHeight - this.oldScrollHeight;
- messageWrapper.scrollTop += heightGained;
+ var heightGained = messageWrapperScroll.scrollHeight - this.oldScrollHeight;
+ messageWrapperScroll.scrollTop += heightGained;
this.oldScrollHeight = undefined;
if (!this.fillSpace()) {
this.setState({paginating: false});
}
} else if (this.atBottom) {
- messageWrapper.scrollTop = messageWrapper.scrollHeight;
+ messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
if (this.state.numUnreadMessages !== 0) {
this.setState({numUnreadMessages: 0});
}
@@ -247,11 +278,11 @@ module.exports = {
fillSpace: function() {
if (!this.refs.messageWrapper) return;
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
- if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
+ var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
+ if (messageWrapperScroll.scrollTop < messageWrapperScroll.clientHeight && this.state.room.oldState.paginationToken) {
this.setState({paginating: true});
- this.oldScrollHeight = messageWrapper.scrollHeight;
+ this.oldScrollHeight = messageWrapperScroll.scrollHeight;
if (this.state.messageCap < this.state.room.timeline.length) {
this.waiting_for_paginate = false;
@@ -278,6 +309,13 @@ module.exports = {
return false;
},
+ onResendAllClick: function() {
+ var eventsToResend = this._getUnsentMessages(this.state.room);
+ eventsToResend.forEach(function(event) {
+ Resend.resend(event);
+ });
+ },
+
onJoinButtonClicked: function(ev) {
var self = this;
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
@@ -298,9 +336,9 @@ module.exports = {
onMessageListScroll: function(ev) {
if (this.refs.messageWrapper) {
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
+ var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
var wasAtBottom = this.atBottom;
- this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
+ this.atBottom = messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <= messageWrapperScroll.clientHeight;
if (this.atBottom && !wasAtBottom) {
this.forceUpdate(); // remove unread msg count
}
@@ -363,8 +401,12 @@ module.exports = {
self.setState({
upload: undefined
});
- }).done(undefined, function() {
- // display error message
+ }).done(undefined, function(error) {
+ var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
+ Modal.createDialog(ErrorDialog, {
+ title: "Failed to upload file",
+ description: error.toString()
+ });
});
},
@@ -390,6 +432,7 @@ module.exports = {
room_events: {
search_term: term,
filter: filter,
+ order_by: "recent",
event_context: {
before_limit: 1,
after_limit: 1,
@@ -403,7 +446,11 @@ module.exports = {
searchResults: data,
});
}, function(error) {
- // TODO: show dialog or something
+ var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
+ Modal.createDialog(ErrorDialog, {
+ title: "Search failed",
+ description: error.toString()
+ });
});
},
@@ -421,7 +468,7 @@ module.exports = {
var eventIds = Object.keys(results);
// XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work?
- var resultList = eventIds.map(function(key) { return results[key]; }).sort(function(a, b) { b.rank - a.rank });
+ var resultList = eventIds.map(function(key) { return results[key]; }); // .sort(function(a, b) { b.rank - a.rank });
for (var i = 0; i < resultList.length; i++) {
var ts1 = resultList[i].result.origin_server_ts;
ret.push(
); // Rank: {resultList[i].rank}
diff --git a/src/skins/vector/css/atoms/MemberAvatar.css b/src/skins/vector/css/atoms/MemberAvatar.css
index fc5fd60d..97dae35f 100644
--- a/src/skins/vector/css/atoms/MemberAvatar.css
+++ b/src/skins/vector/css/atoms/MemberAvatar.css
@@ -19,3 +19,12 @@ limitations under the License.
border-radius: 20px;
}
+.mx_MemberAvatar_initial {
+ position: absolute;
+ color: #fff;
+ text-align: center;
+}
+
+.mx_MemberAvatar_wrapper {
+ position: relative;
+}
\ No newline at end of file
diff --git a/src/skins/vector/css/atoms/RoomAvatar.css b/src/skins/vector/css/atoms/RoomAvatar.css
new file mode 100644
index 00000000..f54a93ee
--- /dev/null
+++ b/src/skins/vector/css/atoms/RoomAvatar.css
@@ -0,0 +1,25 @@
+/*
+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_RoomAvatar {
+}
+
+.mx_RoomAvatar_initial {
+ position: absolute;
+ color: #fff;
+ text-align: center;
+ font-weight: normal ! important;
+}
\ No newline at end of file
diff --git a/src/skins/vector/css/atoms/Spinner.css b/src/skins/vector/css/atoms/Spinner.css
new file mode 100644
index 00000000..1c8aa97d
--- /dev/null
+++ b/src/skins/vector/css/atoms/Spinner.css
@@ -0,0 +1,25 @@
+/*
+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_Spinner {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-align-items: center;
+ -webkit-justify-content: center;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/src/skins/vector/css/gemini-scrollbar.css b/src/skins/vector/css/gemini-scrollbar.css
new file mode 120000
index 00000000..4e3c83ba
--- /dev/null
+++ b/src/skins/vector/css/gemini-scrollbar.css
@@ -0,0 +1 @@
+../../../../node_modules/react-gemini-scrollbar/node_modules/gemini-scrollbar/gemini-scrollbar.css
\ No newline at end of file
diff --git a/src/skins/vector/css/hide.css b/src/skins/vector/css/hide.css
index 7d8ee302..f84a35b3 100644
--- a/src/skins/vector/css/hide.css
+++ b/src/skins/vector/css/hide.css
@@ -1,4 +1,3 @@
-.mx_RoomDropTarget,
.mx_RoomSettings_encrypt,
.mx_CreateRoom_encrypt,
.mx_RightPanel_filebutton
diff --git a/src/skins/vector/css/molecules/EventTile.css b/src/skins/vector/css/molecules/EventTile.css
index 25fe9646..d2d87976 100644
--- a/src/skins/vector/css/molecules/EventTile.css
+++ b/src/skins/vector/css/molecules/EventTile.css
@@ -18,13 +18,13 @@ limitations under the License.
max-width: 100%;
clear: both;
margin-top: 24px;
- margin-left: 56px;
+ margin-left: 65px;
}
.mx_EventTile_avatar {
padding-left: 18px;
padding-right: 12px;
- margin-left: -64px;
+ margin-left: -73px;
margin-top: -4px;
float: left;
}
@@ -78,7 +78,7 @@ limitations under the License.
}
.mx_EventTile_notSent {
- color: #f11;
+ color: #ddd;
}
.mx_EventTile_highlight {
diff --git a/src/skins/vector/css/molecules/MatrixToolbar.css b/src/skins/vector/css/molecules/MatrixToolbar.css
index 99c28240..b545b1ad 100644
--- a/src/skins/vector/css/molecules/MatrixToolbar.css
+++ b/src/skins/vector/css/molecules/MatrixToolbar.css
@@ -15,20 +15,40 @@ limitations under the License.
*/
.mx_MatrixToolbar {
- text-align: center;
- background-color: #ff0064;
+ background-color: #76cfa6;
color: #fff;
- font-weight: bold;
- padding: 6px;
+
+ display: -webkit-box;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ -webkit-align-items: center;
+ align-items: center;
}
-.mx_MatrixToolbar button {
- margin-left: 12px;
+.mx_MatrixToolbar_warning {
+ margin-left: 16px;
+ margin-right: 8px;
+ margin-top: -2px;
+}
+
+.mx_MatrixToolbar_link
+{
+ color: #fff ! important;
+ text-decoration: underline ! important;
+ cursor: pointer;
}
.mx_MatrixToolbar_close {
- float: right;
- margin-top: 3px;
- margin-right: 12px;
+ -webkit-flex: 1;
+ flex: 1;
cursor: pointer;
-}
\ No newline at end of file
+ text-align: right;
+}
+
+.mx_MatrixToolbar_close img {
+ display: block;
+ float: right;
+ margin-right: 10px;
+}
diff --git a/src/skins/vector/css/molecules/MessageComposer.css b/src/skins/vector/css/molecules/MessageComposer.css
index 44e12276..2dbe05b5 100644
--- a/src/skins/vector/css/molecules/MessageComposer.css
+++ b/src/skins/vector/css/molecules/MessageComposer.css
@@ -32,7 +32,7 @@ limitations under the License.
.mx_MessageComposer .mx_MessageComposer_avatar {
display: table-cell;
padding-left: 10px;
- padding-right: 20px;
+ padding-right: 28px;
height: 70px;
}
diff --git a/src/skins/vector/css/molecules/RoomDropTarget.css b/src/skins/vector/css/molecules/RoomDropTarget.css
index c42d4499..4eea49e1 100644
--- a/src/skins/vector/css/molecules/RoomDropTarget.css
+++ b/src/skins/vector/css/molecules/RoomDropTarget.css
@@ -16,12 +16,46 @@ limitations under the License.
.mx_RoomDropTarget {
font-size: 14px;
- text-align: center;
- margin-left: 8px;
- margin-right: 8px;
- padding-top: 16px;
- padding-bottom: 16px;
- background-color: #fbfbfb;
- border: 1px dashed #d7d7d7;
- border-radius: 8px;
+ margin-left: 10px;
+ margin-right: 15px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ border: 1px dashed #76cfa6;
+ color: #454545;
+ background-color: rgba(255,255,255,0.5);
+ border-radius: 4px;
+}
+
+.collapsed .mx_RoomDropTarget {
+ margin-right: 10px;
+}
+
+.mx_RoomDropTarget_placeholder {
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.mx_RoomDropTarget_avatar {
+ background-color: #fff;
+ border-radius: 24px;
+ width: 24px;
+ height: 24px;
+ float: left;
+ margin-left: 7px;
+ margin-right: 7px;
+}
+
+.mx_RoomDropTarget_label {
+ position: relative;
+ margin-top: 3px;
+ line-height: 21px;
+ z-index: 1;
+}
+
+.collapsed .mx_RoomDropTarget_avatar {
+ float: none;
+}
+
+.collapsed .mx_RoomDropTarget_label {
+ display: none;
}
diff --git a/src/skins/vector/css/molecules/RoomHeader.css b/src/skins/vector/css/molecules/RoomHeader.css
index 2eeda241..e86bab2e 100644
--- a/src/skins/vector/css/molecules/RoomHeader.css
+++ b/src/skins/vector/css/molecules/RoomHeader.css
@@ -33,6 +33,7 @@ limitations under the License.
.mx_RoomHeader_leftRow {
height: 48px;
margin-top: 18px;
+ margin-left: -2px;
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
@@ -103,7 +104,7 @@ limitations under the License.
color: #454545;
font-weight: 800;
font-size: 24px;
- padding-left: 8px;
+ padding-left: 19px;
padding-right: 16px;
text-overflow: ellipsis;
}
@@ -153,7 +154,7 @@ limitations under the License.
max-height: 38px;
color: #454545;
font-weight: 300;
- padding-left: 8px;
+ padding-left: 19px;
padding-right: 16px;
overflow: hidden;
text-overflow: ellipsis;
diff --git a/src/skins/vector/css/molecules/RoomTile.css b/src/skins/vector/css/molecules/RoomTile.css
index f2c1daad..4bc71cb8 100644
--- a/src/skins/vector/css/molecules/RoomTile.css
+++ b/src/skins/vector/css/molecules/RoomTile.css
@@ -16,13 +16,13 @@ limitations under the License.
.mx_RoomTile {
cursor: pointer;
- display: table-row;
+ /* This fixes wrapping of long room names, but breaks drag & drop previews */
+ /* display: table-row; */
font-size: 14px;
}
.mx_RoomTile_avatar {
display: table-cell;
- background: #eaf5f0;
padding-right: 8px;
padding-top: 4px;
padding-bottom: 2px;
@@ -39,17 +39,16 @@ limitations under the License.
.mx_RoomTile_name {
display: table-cell;
+ width: 100%;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 16px;
- color: #454545;
- opacity: 0.8;
+ color: rgba(69, 69, 69, 0.8);
}
.mx_RoomTile_invite {
- opacity: 0.5;
- font-weight: normal;
+ color: rgba(69, 69, 69, 0.5);
}
.collapsed .mx_RoomTile_name {
@@ -106,15 +105,16 @@ limitations under the License.
.mx_RoomTile_unread,
.mx_RoomTile_highlight,
-.mx_RoomTile_invited
+.mx_RoomTile_selected
{
font-weight: bold;
}
-.mx_RoomTile_selected {
+.mx_RoomTile_selected .mx_RoomTile_name {
+ color: #76cfa6 ! important;
}
-.mx_RoomTile.mx_RoomTile_selected {
+.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_name {
background: url('img/selected.png');
background-repeat: no-repeat;
background-position: right center;
diff --git a/src/skins/vector/css/molecules/RoomTooltip.css b/src/skins/vector/css/molecules/RoomTooltip.css
index 604c6a56..4e831d48 100644
--- a/src/skins/vector/css/molecules/RoomTooltip.css
+++ b/src/skins/vector/css/molecules/RoomTooltip.css
@@ -21,7 +21,6 @@ limitations under the License.
border-radius: 8px;
background-color: #fff;
z-index: 1000;
- margin-top: 6px;
left: 64px;
padding: 6px;
}
diff --git a/src/skins/vector/css/organisms/LeftPanel.css b/src/skins/vector/css/organisms/LeftPanel.css
index 67f00c35..37de0f0e 100644
--- a/src/skins/vector/css/organisms/LeftPanel.css
+++ b/src/skins/vector/css/organisms/LeftPanel.css
@@ -34,16 +34,21 @@ limitations under the License.
cursor: pointer;
}
-.mx_LeftPanel .mx_RoomList {
+.mx_LeftPanel_callView {
+
+}
+
+.mx_LeftPanel .mx_RoomList_scrollbar {
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
-ms-flex-order: 1;
-webkit-order: 1;
order: 1;
- overflow-y: auto;
-webkit-flex: 1 1 0;
flex: 1 1 0;
+
+ overflow-y: auto;
}
.mx_LeftPanel .mx_BottomLeftMenu {
@@ -53,8 +58,10 @@ limitations under the License.
-webkit-order: 3;
order: 3;
- -webkit-flex: 0 0 126px;
- flex: 0 0 126px;
+ -webkit-flex: 0 0 140px;
+ flex: 0 0 140px;
+
+ background-color: rgba(118,207,166,0.19);
}
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
@@ -62,7 +69,7 @@ limitations under the License.
}
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
- margin-top: 12px;
+ margin-top: 17px;
width: 100%;
}
diff --git a/src/skins/vector/css/organisms/RoomList.css b/src/skins/vector/css/organisms/RoomList.css
index 34ebd1db..7f5e2272 100644
--- a/src/skins/vector/css/organisms/RoomList.css
+++ b/src/skins/vector/css/organisms/RoomList.css
@@ -16,13 +16,7 @@ limitations under the License.
.mx_RoomList {
padding-top: 24px;
-}
-
-.mx_RoomList_invites,
-.mx_RoomList_recents {
- display: table;
- table-layout: fixed;
- width: 100%;
+ padding-bottom: 12px;
}
.mx_RoomList_expandButton {
@@ -31,14 +25,3 @@ limitations under the License.
padding-left: 12px;
padding-right: 12px;
}
-
-.mx_RoomList h2 {
- text-transform: uppercase;
- color: #3d3b39;
- font-weight: 600;
- font-size: 14px;
- padding-left: 12px;
- padding-right: 12px;
- margin-top: 8px;
- margin-bottom: 4px;
-}
diff --git a/src/skins/vector/css/organisms/RoomSubList.css b/src/skins/vector/css/organisms/RoomSubList.css
new file mode 100644
index 00000000..57d23a38
--- /dev/null
+++ b/src/skins/vector/css/organisms/RoomSubList.css
@@ -0,0 +1,45 @@
+/*
+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_RoomSubList {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
+
+.mx_RoomSubList_bottommost {
+ /* XXX: this should really be 100% of the RoomList height, but can't seem to get at it */
+ min-height: 400px;
+}
+
+.mx_RoomSubList_label {
+ text-transform: uppercase;
+ color: #3d3b39;
+ font-weight: 600;
+ font-size: 14px;
+ padding-left: 12px;
+ padding-right: 12px;
+ margin-top: 8px;
+ margin-bottom: 4px;
+}
+
+.mx_RoomSubList_chevron {
+ padding-left: 5px;
+}
+
+.collapsed .mx_RoomSubList_chevron {
+ padding-left: 13px;
+}
diff --git a/src/skins/vector/css/organisms/RoomView.css b/src/skins/vector/css/organisms/RoomView.css
index d564b086..191742f5 100644
--- a/src/skins/vector/css/organisms/RoomView.css
+++ b/src/skins/vector/css/organisms/RoomView.css
@@ -129,7 +129,7 @@ limitations under the License.
clear: both;
margin-top: 32px;
margin-bottom: 8px;
- margin-left: 54px;
+ margin-left: 63px;
padding-bottom: 6px;
border-bottom: 1px solid #eee;
}
@@ -158,18 +158,19 @@ limitations under the License.
order: 4;
width: 100%;
- -webkit-flex: 0 0 36px;
- flex: 0 0 36px;
+ -webkit-flex: 0 0 auto;
+ flex: 0 0 auto;
}
.mx_RoomView_statusAreaBox {
max-width: 960px;
margin: auto;
+ min-height: 36px;
}
.mx_RoomView_statusAreaBox_line {
border-top: 1px solid #eee;
- margin-left: 54px;
+ margin-left: 63px;
height: 1px;
}
@@ -185,16 +186,44 @@ limitations under the License.
vertical-align: middle;
}
+.mx_RoomView_connectionLostBar {
+ margin-top: 19px;
+ height: 58px;
+}
+
+.mx_RoomView_connectionLostBar img {
+ padding-left: 10px;
+ padding-right: 22px;
+ vertical-align: middle;
+ float: left;
+}
+
+.mx_RoomView_connectionLostBar_title {
+ color: #ff0064;
+}
+
+.mx_RoomView_connectionLostBar_desc {
+ color: #454545;
+ font-size: 14px;
+ opacity: 0.5;
+}
+
+.mx_RoomView_resend_link {
+ color: #454545 ! important;
+ text-decoration: underline ! important;
+ cursor: pointer;
+}
+
.mx_RoomView_typingBar {
margin-top: 10px;
- margin-left: 54px;
+ margin-left: 63px;
color: #4a4a4a;
opacity: 0.5;
}
.mx_RoomView_typingImage {
display: inline;
- margin-left: -38px;
+ margin-left: -47px;
margin-top: -4px;
float: left;
}
@@ -214,7 +243,7 @@ limitations under the License.
.mx_RoomView_uploadProgressOuter {
height: 4px;
- margin-left: 54px;
+ margin-left: 63px;
margin-top: -1px;
}
@@ -225,7 +254,7 @@ limitations under the License.
.mx_RoomView_uploadFilename {
margin-top: 5px;
- margin-left: 56px;
+ margin-left: 65px;
opacity: 0.5;
color: #4a4a4a;
}
diff --git a/src/skins/vector/css/pages/MatrixChat.css b/src/skins/vector/css/pages/MatrixChat.css
index f649aa24..e6d7d30b 100644
--- a/src/skins/vector/css/pages/MatrixChat.css
+++ b/src/skins/vector/css/pages/MatrixChat.css
@@ -35,7 +35,7 @@ limitations under the License.
-webkit-order: 1;
order: 1;
- height: 21px;
+ height: 40px;
}
.mx_MatrixChat_toolbarShowing {
@@ -71,8 +71,8 @@ limitations under the License.
background-color: #eaf5f0;
- -webkit-flex: 0 0 230px;
- flex: 0 0 230px;
+ -webkit-flex: 0 0 210px;
+ flex: 0 0 210px;
}
.mx_MatrixChat .mx_LeftPanel.collapsed {
@@ -87,8 +87,8 @@ limitations under the License.
-webkit-order: 2;
order: 2;
- padding-left: 12px;
- padding-right: 12px;
+ padding-left: 25px;
+ padding-right: 22px;
background-color: #fff;
-webkit-flex: 1;
@@ -116,8 +116,8 @@ limitations under the License.
-webkit-order: 3;
order: 3;
- -webkit-flex: 0 0 230px;
- flex: 0 0 230px;
+ -webkit-flex: 0 0 235px;
+ flex: 0 0 235px;
}
.mx_MatrixChat .mx_RightPanel.collapsed {
diff --git a/src/skins/vector/img/cancel-black2.png b/src/skins/vector/img/cancel-black2.png
new file mode 100644
index 00000000..a928c61b
Binary files /dev/null and b/src/skins/vector/img/cancel-black2.png differ
diff --git a/src/skins/vector/img/list-close.png b/src/skins/vector/img/list-close.png
new file mode 100644
index 00000000..82b322f9
Binary files /dev/null and b/src/skins/vector/img/list-close.png differ
diff --git a/src/skins/vector/img/list-open.png b/src/skins/vector/img/list-open.png
new file mode 100644
index 00000000..f8c80631
Binary files /dev/null and b/src/skins/vector/img/list-open.png differ
diff --git a/src/skins/vector/img/warning.png b/src/skins/vector/img/warning.png
new file mode 100644
index 00000000..c5553530
Binary files /dev/null and b/src/skins/vector/img/warning.png differ
diff --git a/src/skins/vector/img/warning2.png b/src/skins/vector/img/warning2.png
new file mode 100644
index 00000000..db0fd4a8
Binary files /dev/null and b/src/skins/vector/img/warning2.png differ
diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js
index 54dbad88..58fbb15d 100644
--- a/src/skins/vector/skindex.js
+++ b/src/skins/vector/skindex.js
@@ -81,6 +81,7 @@ skin['organisms.QuestionDialog'] = require('./views/organisms/QuestionDialog');
skin['organisms.RightPanel'] = require('./views/organisms/RightPanel');
skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory');
skin['organisms.RoomList'] = require('./views/organisms/RoomList');
+skin['organisms.RoomSubList'] = require('./views/organisms/RoomSubList');
skin['organisms.RoomView'] = require('./views/organisms/RoomView');
skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');
diff --git a/src/skins/vector/views/atoms/MemberAvatar.js b/src/skins/vector/views/atoms/MemberAvatar.js
index 69652e1a..c4153b85 100644
--- a/src/skins/vector/views/atoms/MemberAvatar.js
+++ b/src/skins/vector/views/atoms/MemberAvatar.js
@@ -40,6 +40,25 @@ module.exports = React.createClass({
},
render: function() {
+ // XXX: recalculates default avatar url constantly
+ if (this.state.imageUrl === this.defaultAvatarUrl(this.props.member)) {
+ var initial;
+ if (this.props.member.name[0])
+ initial = this.props.member.name[0].toUpperCase();
+ if (initial === '@' && this.props.member.name[1])
+ initial = this.props.member.name[1].toUpperCase();
+
+ return (
+
+ { initial }
+
+
+ );
+ }
return (
- );
+
+ // XXX: recalculates fallback avatar constantly
+ if (this.state.imageUrl === this.getFallbackAvatar()) {
+ var initial;
+ if (this.props.room.name[0])
+ initial = this.props.room.name[0].toUpperCase();
+ if ((initial === '@' || initial === '#') && this.props.room.name[1])
+ initial = this.props.room.name[1].toUpperCase();
+
+ return (
+
+ { initial }
+
+
+ );
+ }
+ else {
+ return
+ }
+
}
});
diff --git a/src/skins/vector/views/atoms/Spinner.js b/src/skins/vector/views/atoms/Spinner.js
index 908f2678..6dfd0c41 100644
--- a/src/skins/vector/views/atoms/Spinner.js
+++ b/src/skins/vector/views/atoms/Spinner.js
@@ -26,7 +26,7 @@ module.exports = React.createClass({
var h = this.props.h || 32;
var imgClass = this.props.imgClassName || "";
return (
-
+
);
diff --git a/src/skins/vector/views/molecules/ChangePassword.js b/src/skins/vector/views/molecules/ChangePassword.js
index 004fed39..32315158 100644
--- a/src/skins/vector/views/molecules/ChangePassword.js
+++ b/src/skins/vector/views/molecules/ChangePassword.js
@@ -27,9 +27,9 @@ module.exports = React.createClass({
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;
+ var old_password = this.refs.old_input.value;
+ var new_password = this.refs.new_input.value;
+ var confirm_password = this.refs.confirm_input.value;
if (new_password != confirm_password) {
this.setState({
state: this.Phases.Error,
diff --git a/src/skins/vector/views/molecules/MatrixToolbar.js b/src/skins/vector/views/molecules/MatrixToolbar.js
index 4a299f14..361e39d6 100644
--- a/src/skins/vector/views/molecules/MatrixToolbar.js
+++ b/src/skins/vector/views/molecules/MatrixToolbar.js
@@ -28,12 +28,20 @@ module.exports = React.createClass({
Notifier.setToolbarHidden(true);
},
+ onClick: function() {
+ var Notifier = sdk.getComponent('organisms.Notifier');
+ Notifier.setEnabled(true);
+ },
+
render: function() {
var EnableNotificationsButton = sdk.getComponent("atoms.EnableNotificationsButton");
return (
- You are not receiving desktop notifications.
-
+
+
+
);
}
diff --git a/src/skins/vector/views/molecules/MessageComposer.js b/src/skins/vector/views/molecules/MessageComposer.js
index 25f69bda..c75aaa14 100644
--- a/src/skins/vector/views/molecules/MessageComposer.js
+++ b/src/skins/vector/views/molecules/MessageComposer.js
@@ -29,7 +29,7 @@ module.exports = React.createClass({
mixins: [MessageComposerController],
onUploadClick: function(ev) {
- this.refs.uploadInput.getDOMNode().click();
+ this.refs.uploadInput.click();
},
onUploadFileSelected: function(ev) {
@@ -38,7 +38,7 @@ module.exports = React.createClass({
if (files && files.length > 0) {
this.props.uploadFile(files[0]);
}
- this.refs.uploadInput.getDOMNode().value = null;
+ this.refs.uploadInput.value = null;
},
onCallClick: function(ev) {
diff --git a/src/skins/vector/views/molecules/MessageContextMenu.js b/src/skins/vector/views/molecules/MessageContextMenu.js
index 995c2c4b..b36d4ccb 100644
--- a/src/skins/vector/views/molecules/MessageContextMenu.js
+++ b/src/skins/vector/views/molecules/MessageContextMenu.js
@@ -22,25 +22,13 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
var sdk = require('matrix-react-sdk')
var Modal = require('matrix-react-sdk/lib/Modal');
+var Resend = require("../../../../Resend");
module.exports = React.createClass({
displayName: 'MessageContextMenu',
onResendClick: function() {
- MatrixClientPeg.get().resendEvent(
- this.props.mxEvent, MatrixClientPeg.get().getRoom(
- this.props.mxEvent.getRoomId()
- )
- ).done(function() {
- dis.dispatch({
- action: 'message_sent'
- });
- }, function() {
- dis.dispatch({
- action: 'message_send_failed'
- });
- });
- dis.dispatch({action: 'message_resend_started'});
+ Resend.resend(this.props.mxEvent);
if (this.props.onFinished) this.props.onFinished();
},
diff --git a/src/skins/vector/views/molecules/RoomDropTarget.js b/src/skins/vector/views/molecules/RoomDropTarget.js
index b1e15077..00d0546c 100644
--- a/src/skins/vector/views/molecules/RoomDropTarget.js
+++ b/src/skins/vector/views/molecules/RoomDropTarget.js
@@ -18,16 +18,25 @@ limitations under the License.
var React = require('react');
-//var RoomDropTargetController = require('matrix-react-sdk/lib/controllers/molecules/RoomDropTargetController')
-
module.exports = React.createClass({
displayName: 'RoomDropTarget',
- // mixins: [RoomDropTargetController],
+
render: function() {
- return (
-
- {this.props.text}
-
- );
+ if (this.props.placeholder) {
+ return (
+
+
+ );
+ }
+ else {
+ return (
+
+
+
+ { this.props.label }
+
+
+ );
+ }
}
});
diff --git a/src/skins/vector/views/molecules/RoomHeader.js b/src/skins/vector/views/molecules/RoomHeader.js
index 7f45fd42..cc43b1cd 100644
--- a/src/skins/vector/views/molecules/RoomHeader.js
+++ b/src/skins/vector/views/molecules/RoomHeader.js
@@ -35,7 +35,7 @@ module.exports = React.createClass({
},
getRoomName: function() {
- return this.refs.name_edit.getDOMNode().value;
+ return this.refs.name_edit.value;
},
onFullscreenClick: function() {
diff --git a/src/skins/vector/views/molecules/RoomSettings.js b/src/skins/vector/views/molecules/RoomSettings.js
index c5e08ff9..4fdd40d9 100644
--- a/src/skins/vector/views/molecules/RoomSettings.js
+++ b/src/skins/vector/views/molecules/RoomSettings.js
@@ -27,15 +27,15 @@ module.exports = React.createClass({
mixins: [RoomSettingsController],
getTopic: function() {
- return this.refs.topic.getDOMNode().value;
+ return this.refs.topic.value;
},
getJoinRules: function() {
- return this.refs.is_private.getDOMNode().checked ? "invite" : "public";
+ return this.refs.is_private.checked ? "invite" : "public";
},
getHistoryVisibility: function() {
- return this.refs.share_history.getDOMNode().checked ? "shared" : "invited";
+ return this.refs.share_history.checked ? "shared" : "invited";
},
getPowerLevels: function() {
@@ -45,13 +45,13 @@ module.exports = React.createClass({
power_levels = power_levels.getContent();
var new_power_levels = {
- ban: parseInt(this.refs.ban.getDOMNode().value),
- kick: parseInt(this.refs.kick.getDOMNode().value),
- redact: parseInt(this.refs.redact.getDOMNode().value),
- invite: parseInt(this.refs.invite.getDOMNode().value),
- events_default: parseInt(this.refs.events_default.getDOMNode().value),
- state_default: parseInt(this.refs.state_default.getDOMNode().value),
- users_default: parseInt(this.refs.users_default.getDOMNode().value),
+ ban: parseInt(this.refs.ban.value),
+ kick: parseInt(this.refs.kick.value),
+ redact: parseInt(this.refs.redact.value),
+ invite: parseInt(this.refs.invite.value),
+ events_default: parseInt(this.refs.events_default.value),
+ state_default: parseInt(this.refs.state_default.value),
+ users_default: parseInt(this.refs.users_default.value),
users: power_levels.users,
events: power_levels.events,
};
diff --git a/src/skins/vector/views/molecules/RoomTile.js b/src/skins/vector/views/molecules/RoomTile.js
index 82616b5a..471bd8a1 100644
--- a/src/skins/vector/views/molecules/RoomTile.js
+++ b/src/skins/vector/views/molecules/RoomTile.js
@@ -17,6 +17,8 @@ limitations under the License.
'use strict';
var React = require('react');
+var DragSource = require('react-dnd').DragSource;
+var DropTarget = require('react-dnd').DropTarget;
var classNames = require('classnames');
var RoomTileController = require('matrix-react-sdk/lib/controllers/molecules/RoomTile')
@@ -25,10 +27,178 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk')
-module.exports = React.createClass({
+/**
+ * Specifies the drag source contract.
+ * Only `beginDrag` function is required.
+ */
+var roomTileSource = {
+ canDrag: function(props, monitor) {
+ return props.roomSubList.props.editable;
+ },
+
+ beginDrag: function (props) {
+ // Return the data describing the dragged item
+ var item = {
+ room: props.room,
+ originalList: props.roomSubList,
+ originalIndex: props.roomSubList.findRoomTile(props.room).index,
+ targetList: props.roomSubList, // at first target is same as original
+ lastTargetRoom: null,
+ lastYOffset: null,
+ lastYDelta: null,
+ };
+
+ if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId);
+
+ // doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions
+ props.room._dragging = true;
+
+ return item;
+ },
+
+ endDrag: function (props, monitor, component) {
+ var item = monitor.getItem();
+
+ if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop());
+
+ props.room._dragging = false;
+ if (monitor.didDrop()) {
+ if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label);
+ item.targetList.forceUpdate(); // as we're not using state
+ }
+
+ if (monitor.didDrop() && item.targetList.props.editable) {
+ // if we moved lists, remove the old tag
+ if (item.targetList !== item.originalList) {
+ // commented out attempts to set a spinner on our target component as component is actually
+ // the original source component being dragged, not our target. To fix we just need to
+ // move all of this to endDrop in the target instead. FIXME later.
+
+ //component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
+ MatrixClientPeg.get().deleteRoomTag(item.room.roomId, item.originalList.props.tagName).finally(function() {
+ //component.state.set({ spinner: component.state.spinner-- });
+ }).fail(function(err) {
+ var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
+ Modal.createDialog(ErrorDialog, {
+ title: "Failed to remove tag " + item.originalList.props.tagName + " from room",
+ description: err.toString()
+ });
+ });
+ }
+
+ var newOrder= {};
+ if (item.targetList.props.order === 'manual') {
+ newOrder['order'] = item.targetList.calcManualOrderTagData(item.room);
+ }
+
+ // if we moved lists or the ordering changed, add the new tag
+ if (item.targetList.props.tagName && (item.targetList !== item.originalList || newOrder)) {
+ //component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
+ MatrixClientPeg.get().setRoomTag(item.room.roomId, item.targetList.props.tagName, newOrder).finally(function() {
+ //component.state.set({ spinner: component.state.spinner-- });
+ }).fail(function(err) {
+ var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
+ Modal.createDialog(ErrorDialog, {
+ title: "Failed to add tag " + item.targetList.props.tagName + " to room",
+ description: err.toString()
+ });
+ });
+ }
+ }
+ else {
+ // cancel the drop and reset our original position
+ if (props.roomSubList.debug) console.log("cancelling drop & drag");
+ props.roomSubList.moveRoomTile(item.room, item.originalIndex);
+ if (item.targetList && item.targetList !== item.originalList) {
+ item.targetList.removeRoomTile(item.room);
+ }
+ }
+ }
+};
+
+var roomTileTarget = {
+ canDrop: function() {
+ return false;
+ },
+
+ hover: function(props, monitor) {
+ var item = monitor.getItem();
+ var off = monitor.getClientOffset();
+ // console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
+
+ //console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList);
+
+ var switchedTarget = false;
+ if (item.targetList !== props.roomSubList) {
+ // we've switched target, so remove the tile from the previous target.
+ // n.b. the previous target might actually be the source list.
+ if (props.roomSubList.debug) console.log("switched target sublist");
+ switchedTarget = true;
+ item.targetList.removeRoomTile(item.room);
+ item.targetList = props.roomSubList;
+ }
+
+ if (!item.targetList.props.editable) return;
+
+ if (item.targetList.props.order === 'manual') {
+ if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) {
+ // find the offset of the target tile in the list.
+ var roomTile = props.roomSubList.findRoomTile(props.room);
+ // shuffle the list to add our tile to that position.
+ props.roomSubList.moveRoomTile(item.room, roomTile.index);
+ }
+
+ // stop us from flickering between our droptarget and the previous room.
+ // whenever the cursor changes direction we have to reset the flicker-damping.
+
+ var yDelta = off.y - item.lastYOffset;
+
+ if ((yDelta > 0 && item.lastYDelta < 0) ||
+ (yDelta < 0 && item.lastYDelta > 0))
+ {
+ // the cursor changed direction - forget our previous room
+ item.lastTargetRoom = null;
+ }
+ else {
+ // track the last room we were hovering over so we can stop
+ // bouncing back and forth if the droptarget is narrower than
+ // the other list items. The other way to do this would be
+ // to reduce the size of the hittarget on the list items, but
+ // can't see an easy way to do that.
+ item.lastTargetRoom = props.room;
+ }
+
+ if (yDelta) item.lastYDelta = yDelta;
+ item.lastYOffset = off.y;
+ }
+ else if (switchedTarget) {
+ if (!props.roomSubList.findRoomTile(item.room).room) {
+ // add to the list in the right place
+ props.roomSubList.moveRoomTile(item.room, 0);
+ }
+ // we have to sort the list whatever to recalculate it
+ props.roomSubList.sortList();
+ }
+ },
+};
+
+var RoomTile = React.createClass({
displayName: 'RoomTile',
mixins: [RoomTileController],
+ propTypes: {
+ connectDragSource: React.PropTypes.func.isRequired,
+ connectDropTarget: React.PropTypes.func.isRequired,
+ isDragging: React.PropTypes.bool.isRequired,
+ room: React.PropTypes.object.isRequired,
+ collapsed: React.PropTypes.bool.isRequired,
+ selected: React.PropTypes.bool.isRequired,
+ unread: React.PropTypes.bool.isRequired,
+ highlight: React.PropTypes.bool.isRequired,
+ isInvite: React.PropTypes.bool.isRequired,
+ roomSubList: React.PropTypes.object.isRequired,
+ },
+
getInitialState: function() {
return( { hover : false });
},
@@ -42,21 +212,32 @@ module.exports = React.createClass({
},
render: function() {
+ // if (this.props.clientOffset) {
+ // //console.log("room " + this.props.room.roomId + " has dropTarget clientOffset " + this.props.clientOffset.x + "," + this.props.clientOffset.y);
+ // }
+
+ if (this.props.room._dragging) {
+ var RoomDropTarget = sdk.getComponent("molecules.RoomDropTarget");
+ return
;
+ }
+
var myUserId = MatrixClientPeg.get().credentials.userId;
+ var me = this.props.room.currentState.members[myUserId];
var classes = classNames({
'mx_RoomTile': true,
'mx_RoomTile_selected': this.props.selected,
'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_highlight': this.props.highlight,
- 'mx_RoomTile_invited': this.props.room.currentState.members[myUserId].membership == 'invite'
+ 'mx_RoomTile_invited': (me && me.membership == 'invite'),
});
var name;
if (this.props.isInvite) {
- name = this.props.room.getMember(MatrixClientPeg.get().credentials.userId).events.member.getSender();
+ name = this.props.room.getMember(myUserId).events.member.getSender();
}
else {
- name = this.props.room.name;
+ // XXX: We should never display raw room IDs, but sometimes the room name js sdk gives is undefined
+ name = this.props.room.name || this.props.room.roomId;
}
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
@@ -91,7 +272,14 @@ module.exports = React.createClass({
}
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
- return (
+
+ // These props are injected by React DnD,
+ // as defined by your `collect` function above:
+ var isDragging = this.props.isDragging;
+ var connectDragSource = this.props.connectDragSource;
+ var connectDropTarget = this.props.connectDropTarget;
+
+ return connectDragSource(connectDropTarget(
@@ -99,6 +287,27 @@ module.exports = React.createClass({
{ label }
- );
+ ));
}
});
+
+// Export the wrapped version, inlining the 'collect' functions
+// to more closely resemble the ES7
+module.exports =
+DropTarget('RoomTile', roomTileTarget, function(connect, monitor) {
+ return {
+ // Call this function inside render()
+ // to let React DnD handle the drag events:
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ }
+})(
+DragSource('RoomTile', roomTileSource, function(connect, monitor) {
+ return {
+ // Call this function inside render()
+ // to let React DnD handle the drag events:
+ connectDragSource: connect.dragSource(),
+ // You can ask the monitor about the current drag state:
+ isDragging: monitor.isDragging()
+ };
+})(RoomTile));
\ No newline at end of file
diff --git a/src/skins/vector/views/molecules/RoomTooltip.js b/src/skins/vector/views/molecules/RoomTooltip.js
index 82e3e744..51b13526 100644
--- a/src/skins/vector/views/molecules/RoomTooltip.js
+++ b/src/skins/vector/views/molecules/RoomTooltip.js
@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
+var ReactDOM = require('react-dom');
var dis = require('matrix-react-sdk/lib/dispatcher');
@@ -24,21 +25,21 @@ module.exports = React.createClass({
displayName: 'RoomTooltip',
componentDidMount: function() {
+ var tooltip = ReactDOM.findDOMNode(this);
if (!this.props.bottom) {
// tell the roomlist about us so it can position us
dis.dispatch({
action: 'view_tooltip',
- tooltip: this.getDOMNode(),
+ tooltip: tooltip,
});
}
else {
- var tooltip = this.getDOMNode();
tooltip.style.top = tooltip.parentElement.getBoundingClientRect().top + "px";
tooltip.style.display = "block";
}
},
- componentDidUnmount: function() {
+ componentWillUnmount: function() {
if (!this.props.bottom) {
dis.dispatch({
action: 'view_tooltip',
diff --git a/src/skins/vector/views/molecules/SearchBar.js b/src/skins/vector/views/molecules/SearchBar.js
index d31e24b4..585b9a6d 100644
--- a/src/skins/vector/views/molecules/SearchBar.js
+++ b/src/skins/vector/views/molecules/SearchBar.js
@@ -39,7 +39,7 @@ module.exports = React.createClass({
onSearchChange: function(e) {
if (e.keyCode === 13) { // on enter...
- this.props.onSearch(this.refs.search_term.getDOMNode().value, this.state.scope);
+ this.props.onSearch(this.refs.search_term.value, this.state.scope);
}
},
diff --git a/src/skins/vector/views/molecules/UserSelector.js b/src/skins/vector/views/molecules/UserSelector.js
index 6b233690..58cb7d21 100644
--- a/src/skins/vector/views/molecules/UserSelector.js
+++ b/src/skins/vector/views/molecules/UserSelector.js
@@ -25,8 +25,8 @@ module.exports = React.createClass({
mixins: [UserSelectorController],
onAddUserId: function() {
- this.addUser(this.refs.user_id_input.getDOMNode().value);
- this.refs.user_id_input.getDOMNode().value = "";
+ this.addUser(this.refs.user_id_input.value);
+ this.refs.user_id_input.value = "";
},
render: function() {
diff --git a/src/skins/vector/views/molecules/voip/CallView.js b/src/skins/vector/views/molecules/voip/CallView.js
index 07987bd3..52297bbc 100644
--- a/src/skins/vector/views/molecules/voip/CallView.js
+++ b/src/skins/vector/views/molecules/voip/CallView.js
@@ -34,7 +34,7 @@ module.exports = React.createClass({
render: function(){
var VideoView = sdk.getComponent('molecules.voip.VideoView');
return (
-
+
);
}
});
diff --git a/src/skins/vector/views/molecules/voip/IncomingCallBox.js b/src/skins/vector/views/molecules/voip/IncomingCallBox.js
index c3bcd825..bf129904 100644
--- a/src/skins/vector/views/molecules/voip/IncomingCallBox.js
+++ b/src/skins/vector/views/molecules/voip/IncomingCallBox.js
@@ -27,7 +27,7 @@ module.exports = React.createClass({
mixins: [IncomingCallBoxController],
getRingAudio: function() {
- return this.refs.ringAudio.getDOMNode();
+ return this.refs.ringAudio;
},
render: function() {
diff --git a/src/skins/vector/views/molecules/voip/VideoView.js b/src/skins/vector/views/molecules/voip/VideoView.js
index 4e0fb913..75a2500d 100644
--- a/src/skins/vector/views/molecules/voip/VideoView.js
+++ b/src/skins/vector/views/molecules/voip/VideoView.js
@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
+var ReactDOM = require('react-dom');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher')
@@ -29,15 +30,15 @@ module.exports = React.createClass({
},
getRemoteVideoElement: function() {
- return this.refs.remote.getDOMNode();
+ return ReactDOM.findDOMNode(this.refs.remote);
},
getRemoteAudioElement: function() {
- return this.refs.remoteAudio.getDOMNode();
+ return this.refs.remoteAudio;
},
getLocalVideoElement: function() {
- return this.refs.local.getDOMNode();
+ return ReactDOM.findDOMNode(this.refs.local);
},
setContainer: function(c) {
@@ -50,7 +51,7 @@ module.exports = React.createClass({
if (!this.container) {
return;
}
- var element = this.container.getDOMNode();
+ var element = this.container;
if (payload.fullscreen) {
var requestMethod = (
element.requestFullScreen ||
@@ -78,7 +79,7 @@ module.exports = React.createClass({
render: function() {
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
return (
-
+
diff --git a/src/skins/vector/views/organisms/LeftPanel.js b/src/skins/vector/views/organisms/LeftPanel.js
index ec25f934..96d48e0e 100644
--- a/src/skins/vector/views/organisms/LeftPanel.js
+++ b/src/skins/vector/views/organisms/LeftPanel.js
@@ -17,18 +17,72 @@ limitations under the License.
'use strict';
var React = require('react');
+var DragDropContext = require('react-dnd').DragDropContext;
+var HTML5Backend = require('react-dnd-html5-backend');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
-module.exports = React.createClass({
+var CallHandler = require("matrix-react-sdk/lib/CallHandler");
+
+var LeftPanel = React.createClass({
displayName: 'LeftPanel',
+ getInitialState: function() {
+ return {
+ showCallElement: null,
+ };
+ },
+
+ componentDidMount: function() {
+ this.dispatcherRef = dis.register(this.onAction);
+ },
+
+ componentWillReceiveProps: function(newProps) {
+ this._recheckCallElement(newProps.selectedRoom);
+ },
+
+ componentWillUnmount: function() {
+ dis.unregister(this.dispatcherRef);
+ },
+
+ onAction: function(payload) {
+ switch (payload.action) {
+ // listen for call state changes to prod the render method, which
+ // may hide the global CallView if the call it is tracking is dead
+ case 'call_state':
+ this._recheckCallElement(this.props.selectedRoom);
+ break;
+ }
+ },
+
+ _recheckCallElement: function(selectedRoomId) {
+ // if we aren't viewing a room with an ongoing call, but there is an
+ // active call, show the call element - we need to do this to make
+ // audio/video not crap out
+ var activeCall = CallHandler.getAnyActiveCall();
+ var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
+ var showCall = (activeCall && !callForRoom);
+ this.setState({
+ showCallElement: showCall
+ });
+ },
+
onHideClick: function() {
dis.dispatch({
action: 'hide_left_panel',
});
},
+ onCallViewClick: function() {
+ var call = CallHandler.getAnyActiveCall();
+ if (call) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: call.roomId,
+ });
+ }
+ },
+
render: function() {
var RoomList = sdk.getComponent('organisms.RoomList');
var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu');
@@ -44,10 +98,17 @@ module.exports = React.createClass({
// collapseButton =
}
+ var callPreview;
+ if (this.state.showCallElement) {
+ var CallView = sdk.getComponent('molecules.voip.CallView');
+ callPreview =
+ }
+
return (
{ collapseButton }
+ { callPreview }
@@ -55,3 +116,4 @@ module.exports = React.createClass({
}
});
+module.exports = DragDropContext(HTML5Backend)(LeftPanel);
diff --git a/src/skins/vector/views/organisms/MemberList.js b/src/skins/vector/views/organisms/MemberList.js
index ba7bc010..407e282a 100644
--- a/src/skins/vector/views/organisms/MemberList.js
+++ b/src/skins/vector/views/organisms/MemberList.js
@@ -21,6 +21,7 @@ var classNames = require('classnames');
var Loader = require('react-loader');
var MemberListController = require('matrix-react-sdk/lib/controllers/organisms/MemberList')
+var GeminiScrollbar = require('react-gemini-scrollbar');
var sdk = require('matrix-react-sdk')
@@ -71,7 +72,7 @@ module.exports = React.createClass({
},
onPopulateInvite: function(e) {
- this.onInvite(this.refs.invite.getDOMNode().value);
+ this.onInvite(this.refs.invite.value);
e.preventDefault();
},
@@ -104,7 +105,7 @@ module.exports = React.createClass({
}
return (
-
+
{this.inviteTile()}
@@ -112,7 +113,7 @@ module.exports = React.createClass({
{invitedSection}
-
+
);
}
diff --git a/src/skins/vector/views/organisms/RoomDirectory.js b/src/skins/vector/views/organisms/RoomDirectory.js
index 90c5dd57..8c4dc51d 100644
--- a/src/skins/vector/views/organisms/RoomDirectory.js
+++ b/src/skins/vector/views/organisms/RoomDirectory.js
@@ -110,9 +110,9 @@ module.exports = React.createClass({
onKeyUp: function(ev) {
this.forceUpdate();
- this.setState({ roomAlias : this.refs.roomAlias.getDOMNode().value })
+ this.setState({ roomAlias : this.refs.roomAlias.value })
if (ev.key == "Enter") {
- this.joinRoom(this.refs.roomAlias.getDOMNode().value);
+ this.joinRoom(this.refs.roomAlias.value);
}
if (ev.key == "Down") {
diff --git a/src/skins/vector/views/organisms/RoomList.js b/src/skins/vector/views/organisms/RoomList.js
index dc958a4e..018cc9b0 100644
--- a/src/skins/vector/views/organisms/RoomList.js
+++ b/src/skins/vector/views/organisms/RoomList.js
@@ -20,6 +20,7 @@ var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
+var GeminiScrollbar = require('react-gemini-scrollbar');
var RoomListController = require('../../../../controllers/organisms/RoomList')
module.exports = React.createClass({
@@ -33,48 +34,82 @@ module.exports = React.createClass({
},
render: function() {
- var CallView = sdk.getComponent('molecules.voip.CallView');
- var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
-
- var callElement;
- if (this.state.show_call_element) {
- callElement =
- }
-
var expandButton = this.props.collapsed ?
:
null;
- var invitesLabel = this.props.collapsed ? null : "Invites";
- var recentsLabel = this.props.collapsed ? null : "Recent";
-
- var invites;
- if (this.state.inviteList.length) {
- invites =
-
{ invitesLabel }
-
- {this.makeRoomTiles(this.state.inviteList, true)}
-
-
- }
+ var RoomSubList = sdk.getComponent('organisms.RoomSubList');
+ var self = this;
return (
-
+
+
{ expandButton }
- { callElement }
-
Favourites
-
- { invites }
+
-
{ recentsLabel }
-
- {this.makeRoomTiles(this.state.roomList, false)}
-
+
-
Archive
-
+
+
+ { Object.keys(self.state.lists).map(function(tagName) {
+ if (!tagName.match(/^m\.(invite|favourite|recent|lowpriority|archived)$/)) {
+ return
+
+ }
+ }) }
+
+
+
+
+
);
}
});
diff --git a/src/skins/vector/views/organisms/RoomSubList.js b/src/skins/vector/views/organisms/RoomSubList.js
new file mode 100644
index 00000000..a4fd5e99
--- /dev/null
+++ b/src/skins/vector/views/organisms/RoomSubList.js
@@ -0,0 +1,290 @@
+/*
+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 DropTarget = require('react-dnd').DropTarget;
+var sdk = require('matrix-react-sdk')
+var dis = require('matrix-react-sdk/lib/dispatcher');
+
+// turn this on for drop & drag console debugging galore
+var debug = false;
+
+var roomListTarget = {
+ canDrop: function() {
+ return true;
+ },
+
+ drop: function(props, monitor, component) {
+ if (debug) console.log("dropped on sublist")
+ },
+
+ hover: function(props, monitor, component) {
+ var item = monitor.getItem();
+
+ if (component.state.sortedList.length == 0 && props.editable) {
+ if (debug) console.log("hovering on sublist " + props.label + ", isOver=" + monitor.isOver());
+
+ if (item.targetList !== component) {
+ item.targetList.removeRoomTile(item.room);
+ item.targetList = component;
+ }
+
+ component.moveRoomTile(item.room, 0);
+ }
+ },
+};
+
+var RoomSubList = React.createClass({
+ displayName: 'RoomSubList',
+
+ debug: debug,
+
+ propTypes: {
+ list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ label: React.PropTypes.string.isRequired,
+ tagName: React.PropTypes.string,
+ editable: React.PropTypes.bool,
+ order: React.PropTypes.string.isRequired,
+ bottommost: React.PropTypes.bool,
+ selectedRoom: React.PropTypes.string.isRequired,
+ activityMap: React.PropTypes.object.isRequired,
+ collapsed: React.PropTypes.bool.isRequired
+ },
+
+ getInitialState: function() {
+ return {
+ hidden: false,
+ sortedList: [],
+ };
+ },
+
+ componentWillMount: function() {
+ this.sortList(this.props.list, this.props.order);
+ },
+
+ componentWillReceiveProps: function(newProps) {
+ // order the room list appropriately before we re-render
+ //if (debug) console.log("received new props, list = " + newProps.list);
+ this.sortList(newProps.list, newProps.order);
+ },
+
+ onClick: function(ev) {
+ this.setState({ hidden : !this.state.hidden });
+ },
+
+ tsOfNewestEvent: function(room) {
+ if (room.timeline.length) {
+ return room.timeline[room.timeline.length - 1].getTs();
+ }
+ else {
+ return Number.MAX_SAFE_INTEGER;
+ }
+ },
+
+ // TODO: factor the comparators back out into a generic comparator
+ // so that view_prev_room and view_next_room can do the right thing
+
+ recentsComparator: function(roomA, roomB) {
+ return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
+ },
+
+ manualComparator: function(roomA, roomB) {
+ if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
+ var a = roomA.tags[this.props.tagName].order;
+ var b = roomB.tags[this.props.tagName].order;
+ return a == b ? this.recentsComparator(roomA, roomB) : ( a > b ? 1 : -1);
+ },
+
+ sortList: function(list, order) {
+ if (list === undefined) list = this.state.sortedList;
+ if (order === undefined) order = this.props.order;
+ var comparator;
+ list = list || [];
+ if (order === "manual") comparator = this.manualComparator;
+ if (order === "recent") comparator = this.recentsComparator;
+
+ //if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
+ this.setState({ sortedList: list.sort(comparator) });
+ },
+
+ moveRoomTile: function(room, atIndex) {
+ if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
+ //console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
+ var found = this.findRoomTile(room);
+ var rooms = this.state.sortedList;
+ if (found.room) {
+ if (debug) console.log("removing at index " + found.index + " and adding at index " + atIndex);
+ rooms.splice(found.index, 1);
+ rooms.splice(atIndex, 0, found.room);
+ }
+ else {
+ if (debug) console.log("Adding at index " + atIndex);
+ rooms.splice(atIndex, 0, room);
+ }
+ this.setState({ sortedList: rooms });
+ // console.log("moveRoomTile after: " + JSON.stringify(this.state.rooms));
+ },
+
+ // XXX: this isn't invoked via a property method but indirectly via
+ // the roomList property method. Unsure how evil this is.
+ removeRoomTile: function(room) {
+ if (debug) console.log("remove room " + room.roomId);
+ var found = this.findRoomTile(room);
+ var rooms = this.state.sortedList;
+ if (found.room) {
+ rooms.splice(found.index, 1);
+ }
+ else {
+ console.warn("Can't remove room " + room.roomId + " - can't find it");
+ }
+ this.setState({ sortedList: rooms });
+ },
+
+ findRoomTile: function(room) {
+ var index = this.state.sortedList.indexOf(room);
+ if (index >= 0) {
+ // console.log("found: room: " + room.roomId + " with index " + index);
+ }
+ else {
+ if (debug) console.log("didn't find room");
+ room = null;
+ }
+ return ({
+ room: room,
+ index: index,
+ });
+ },
+
+ calcManualOrderTagData: function(room) {
+ var index = this.state.sortedList.indexOf(room);
+
+ // we sort rooms by the lexicographic ordering of the 'order' metadata on their tags.
+ // for convenience, we calculate this for now a floating point number between 0.0 and 1.0.
+
+ var orderA = 0.0; // by default we're next to the beginning of the list
+ if (index > 0) {
+ var prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
+ if (!prevTag) {
+ console.error("Previous room in sublist is not tagged to be in this list. This should never happen.")
+ }
+ else if (prevTag.order === undefined) {
+ console.error("Previous room in sublist has no ordering metadata. This should never happen.");
+ }
+ else {
+ orderA = prevTag.order;
+ }
+ }
+
+ var orderB = 1.0; // by default we're next to the end of the list too
+ if (index < this.state.sortedList.length - 1) {
+ var nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
+ if (!nextTag) {
+ console.error("Next room in sublist is not tagged to be in this list. This should never happen.")
+ }
+ else if (nextTag.order === undefined) {
+ console.error("Next room in sublist has no ordering metadata. This should never happen.");
+ }
+ else {
+ orderB = nextTag.order;
+ }
+ }
+
+ var order = (orderA + orderB) / 2.0;
+ if (order === orderA || order === orderB) {
+ console.error("Cannot describe new list position. This should be incredibly unlikely.");
+ // TODO: renumber the list
+ }
+
+ return order;
+ },
+
+ makeRoomTiles: function() {
+ var self = this;
+ var RoomTile = sdk.getComponent("molecules.RoomTile");
+ return this.state.sortedList.map(function(room) {
+ var selected = room.roomId == self.props.selectedRoom;
+ // XXX: is it evil to pass in self as a prop to RoomTile?
+ return (
+
+ );
+ });
+ },
+
+ render: function() {
+ var connectDropTarget = this.props.connectDropTarget;
+ var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
+
+ var label = this.props.collapsed ? null : this.props.label;
+
+ //console.log("render: " + JSON.stringify(this.state.sortedList));
+
+ var target;
+ if (this.state.sortedList.length == 0 && this.props.editable) {
+ target =
;
+ }
+
+ if (this.state.sortedList.length > 0 || this.props.editable) {
+ var subList;
+ var classes = "mx_RoomSubList" +
+ (this.props.bottommost ? " mx_RoomSubList_bottommost" : "");
+
+ if (!this.state.hidden) {
+ subList =
+ { target }
+ { this.makeRoomTiles() }
+
;
+ }
+ else {
+ subList =
+
;
+ }
+
+ return connectDropTarget(
+
+
{ this.props.collapsed ? '' : this.props.label }
+
+
+ { subList }
+
+ );
+ }
+ else {
+ return (
+
+
+ );
+ }
+ }
+});
+
+// Export the wrapped version, inlining the 'collect' functions
+// to more closely resemble the ES7
+module.exports =
+DropTarget('RoomTile', roomListTarget, function(connect) {
+ return {
+ connectDropTarget: connect.dropTarget(),
+ }
+})(RoomSubList);
diff --git a/src/skins/vector/views/organisms/RoomView.js b/src/skins/vector/views/organisms/RoomView.js
index 2dc63cac..dfab5b67 100644
--- a/src/skins/vector/views/organisms/RoomView.js
+++ b/src/skins/vector/views/organisms/RoomView.js
@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
+var ReactDOM = require('react-dom');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
@@ -25,6 +26,7 @@ var sdk = require('matrix-react-sdk')
var classNames = require("classnames");
var filesize = require('filesize');
+var GeminiScrollbar = require('react-gemini-scrollbar');
var RoomViewController = require('../../../../controllers/organisms/RoomView')
var Loader = require("react-loader");
@@ -103,7 +105,7 @@ module.exports = React.createClass({
scrollToBottom: function() {
if (!this.refs.messageWrapper) return;
- var messageWrapper = this.refs.messageWrapper.getDOMNode();
+ var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
messageWrapper.scrollTop = messageWrapper.scrollHeight;
},
@@ -196,10 +198,48 @@ module.exports = React.createClass({
);
} else {
var typingString = this.getWhoIsTypingString();
+ //typingString = "Testing typing...";
var unreadMsgs = this.getUnreadMessagesString();
+ // no conn bar trumps unread count since you can't get unread messages
+ // without a connection! (technically may already have some but meh)
+ // It also trumps the "some not sent" msg since you can't resend without
+ // a connection!
+ if (this.state.syncState === "ERROR") {
+ statusBar = (
+
+
+
+
+ Connectivity to the server has been lost.
+
+
+ Sent messages will be stored until your connection has returned.
+
+
+
+ );
+ }
+ else if (this.state.hasUnsentMessages) {
+ statusBar = (
+
+
+
+
+ Some of your messages have not been sent.
+
+
+
+
+ );
+ }
// unread count trumps who is typing since the unread count is only
// set when you've scrolled up
- if (unreadMsgs) {
+ else if (unreadMsgs) {
statusBar = (
@@ -230,9 +270,13 @@ module.exports = React.createClass({
var conferenceCallNotification = null;
if (this.state.displayConfCallNotification) {
+ var supportedText;
+ if (!MatrixClientPeg.get().supportsVoip()) {
+ supportedText = " (unsupported)";
+ }
conferenceCallNotification = (
- Ongoing conference call
+ Ongoing conference call {supportedText}
);
}
@@ -256,7 +300,7 @@ module.exports = React.createClass({
{ conferenceCallNotification }
{ aux }
-
+
{ fileDropTarget }
@@ -265,7 +309,7 @@ module.exports = React.createClass({
{this.getEventTiles()}
-
+
diff --git a/src/skins/vector/views/pages/MatrixChat.js b/src/skins/vector/views/pages/MatrixChat.js
index 0cf754c2..0553c25a 100644
--- a/src/skins/vector/views/pages/MatrixChat.js
+++ b/src/skins/vector/views/pages/MatrixChat.js
@@ -21,9 +21,6 @@ var sdk = require('matrix-react-sdk')
var MatrixChatController = require('matrix-react-sdk/lib/controllers/pages/MatrixChat')
-// should be atomised
-var Loader = require("react-loader");
-
var dis = require('matrix-react-sdk/lib/dispatcher');
var Matrix = require("matrix-js-sdk");
var ContextualMenu = require("../../../../ContextualMenu");
@@ -154,8 +151,9 @@ module.exports = React.createClass({
);
}
} else if (this.state.logged_in) {
+ var Spinner = sdk.getComponent('atoms.Spinner');
return (
-
+
);
} else if (this.state.screen == 'register') {
return (
@@ -171,4 +169,4 @@ module.exports = React.createClass({
);
}
}
-});
+});
\ No newline at end of file
diff --git a/src/skins/vector/views/templates/Login.js b/src/skins/vector/views/templates/Login.js
index 0378153a..50b09178 100644
--- a/src/skins/vector/views/templates/Login.js
+++ b/src/skins/vector/views/templates/Login.js
@@ -70,8 +70,8 @@ module.exports = React.createClass({
*/
getFormVals: function() {
return {
- 'username': this.refs.user.getDOMNode().value.trim(),
- 'password': this.refs.pass.getDOMNode().value.trim()
+ 'username': this.refs.user.value.trim(),
+ 'password': this.refs.pass.value.trim()
};
},
diff --git a/src/skins/vector/views/templates/Register.js b/src/skins/vector/views/templates/Register.js
index 24f88b05..4490522e 100644
--- a/src/skins/vector/views/templates/Register.js
+++ b/src/skins/vector/views/templates/Register.js
@@ -44,10 +44,10 @@ module.exports = React.createClass({
getRegFormVals: function() {
return {
- email: this.refs.email.getDOMNode().value.trim(),
- username: this.refs.username.getDOMNode().value.trim(),
- password: this.refs.password.getDOMNode().value.trim(),
- confirmPassword: this.refs.confirmPassword.getDOMNode().value.trim()
+ email: this.refs.email.value.trim(),
+ username: this.refs.username.value.trim(),
+ password: this.refs.password.value.trim(),
+ confirmPassword: this.refs.confirmPassword.value.trim()
};
},
diff --git a/src/vector/index.js b/src/vector/index.js
index 29886197..87cbd0b6 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -18,6 +18,7 @@ limitations under the License.
var RunModernizrTests = require("./modernizr"); // this side-effects a global
var React = require("react");
+var ReactDOM = require("react-dom");
var sdk = require("matrix-react-sdk");
sdk.loadSkin(require('../skins/vector/skindex'));
sdk.loadModule(require('../modules/VectorConferenceHandler'));
@@ -65,14 +66,21 @@ function parseQsFromFragment(location) {
return {};
}
+function parseQs(location) {
+ return qs.parse(location.search.substring(1));
+}
+
// Here, we do some crude URL analysis to allow
// deep-linking. We only support registration
// deep-links in this example.
function routeUrl(location) {
- if (location.hash.indexOf('#/register') == 0) {
+ var params = parseQs(location);
+ var loginToken = params.loginToken;
+ if (loginToken) {
+ window.matrixChat.showScreen('token_login', parseQs(location));
+ }
+ else if (location.hash.indexOf('#/register') == 0) {
window.matrixChat.showScreen('register', parseQsFromFragment(location));
- } else if (location.hash.indexOf('#/login/cas') == 0) {
- window.matrixChat.showScreen('cas_login', parseQsFromFragment(location));
} else {
window.matrixChat.showScreen(location.hash.substring(2));
}
@@ -129,7 +137,7 @@ window.onload = function() {
function loadApp() {
if (validBrowser) {
var MatrixChat = sdk.getComponent('pages.MatrixChat');
- window.matrixChat = React.render(
+ window.matrixChat = ReactDOM.render(
,
document.getElementById('matrixchat')
);
@@ -138,7 +146,7 @@ function loadApp() {
console.error("Browser is missing required features.");
// take to a different landing page to AWOOOOOGA at the user
var CompatibilityPage = require("../skins/vector/views/pages/CompatibilityPage");
- window.matrixChat = React.render(
+ window.matrixChat = ReactDOM.render(
Vector
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+