Merge pull request #342 from vector-im/matthew/orderable-roomlist
Implement reorderable rooms via room tagging.
This commit is contained in:
commit
6d9817e5e7
28 changed files with 859 additions and 162 deletions
|
@ -34,7 +34,8 @@
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^0.13.3",
|
"react": "^0.13.3",
|
||||||
"react-loader": "^1.4.0",
|
"react-loader": "^1.4.0",
|
||||||
"sanitize-html": "^1.11.1"
|
"react-dnd": "^1.1.8",
|
||||||
|
"sanitize-html": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel": "^5.8.23",
|
"babel": "^5.8.23",
|
||||||
|
|
|
@ -23,16 +23,23 @@ var dis = require("matrix-react-sdk/lib/dispatcher");
|
||||||
|
|
||||||
var sdk = require('matrix-react-sdk');
|
var sdk = require('matrix-react-sdk');
|
||||||
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
|
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
|
||||||
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
|
|
||||||
|
|
||||||
var HIDE_CONFERENCE_CHANS = true;
|
var HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
activityMap: null,
|
||||||
|
lists: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
cli.on("Room", this.onRoom);
|
cli.on("Room", this.onRoom);
|
||||||
cli.on("Room.timeline", this.onRoomTimeline);
|
cli.on("Room.timeline", this.onRoomTimeline);
|
||||||
cli.on("Room.name", this.onRoomName);
|
cli.on("Room.name", this.onRoomName);
|
||||||
|
cli.on("Room.tags", this.onRoomTags);
|
||||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||||
|
|
||||||
|
@ -47,11 +54,6 @@ module.exports = {
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
switch (payload.action) {
|
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':
|
case 'view_tooltip':
|
||||||
this.tooltip = payload.tooltip;
|
this.tooltip = payload.tooltip;
|
||||||
this._repositionTooltip();
|
this._repositionTooltip();
|
||||||
|
@ -72,7 +74,6 @@ module.exports = {
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
componentWillReceiveProps: function(newProps) {
|
||||||
this.state.activityMap[newProps.selectedRoom] = undefined;
|
this.state.activityMap[newProps.selectedRoom] = undefined;
|
||||||
this._recheckCallElement(newProps.selectedRoom);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
activityMap: this.state.activityMap
|
activityMap: this.state.activityMap
|
||||||
});
|
});
|
||||||
|
@ -109,6 +110,10 @@ module.exports = {
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onRoomTags: function(event, room) {
|
||||||
|
this.refreshRoomList();
|
||||||
|
},
|
||||||
|
|
||||||
onRoomStateEvents: function(ev, state) {
|
onRoomStateEvents: function(ev, state) {
|
||||||
setTimeout(this.refreshRoomList, 0);
|
setTimeout(this.refreshRoomList, 0);
|
||||||
},
|
},
|
||||||
|
@ -117,26 +122,36 @@ module.exports = {
|
||||||
setTimeout(this.refreshRoomList, 0);
|
setTimeout(this.refreshRoomList, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
refreshRoomList: function() {
|
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());
|
this.setState(this.getRoomLists());
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoomLists: function() {
|
getRoomLists: function() {
|
||||||
var s = {};
|
var s = { lists: {} };
|
||||||
var inviteList = [];
|
|
||||||
s.roomList = RoomListSorter.mostRecentActivityFirst(
|
|
||||||
MatrixClientPeg.get().getRooms().filter(function(room) {
|
|
||||||
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
|
||||||
|
|
||||||
if (me && me.membership == "invite") {
|
s.lists["m.invite"] = [];
|
||||||
inviteList.push(room);
|
s.lists["m.favourite"] = [];
|
||||||
return false;
|
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 = (
|
var shouldShowRoom = (
|
||||||
me && (me.membership == "join")
|
me && (me.membership == "join")
|
||||||
);
|
);
|
||||||
|
|
||||||
// hiding conf rooms only ever toggles shouldShowRoom to false
|
// hiding conf rooms only ever toggles shouldShowRoom to false
|
||||||
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
|
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
|
||||||
// we want to hide the 1:1 conf<->user room and not the group chat
|
// we want to hide the 1:1 conf<->user room and not the group chat
|
||||||
|
@ -151,23 +166,28 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shouldShowRoom;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
s.inviteList = RoomListSorter.mostRecentActivityFirst(inviteList);
|
|
||||||
return s;
|
|
||||||
},
|
|
||||||
|
|
||||||
_recheckCallElement: function(selectedRoomId) {
|
if (shouldShowRoom) {
|
||||||
// if we aren't viewing a room with an ongoing call, but there is an
|
var tagNames = Object.keys(room.tags);
|
||||||
// active call, show the call element - we need to do this to make
|
if (tagNames.length) {
|
||||||
// audio/video not crap out
|
for (var i = 0; i < tagNames.length; i++) {
|
||||||
var activeCall = CallHandler.getAnyActiveCall();
|
var tagName = tagNames[i];
|
||||||
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
|
s.lists[tagName] = s.lists[tagName] || [];
|
||||||
var showCall = (activeCall && !callForRoom);
|
s.lists[tagNames[i]].push(room);
|
||||||
this.setState({
|
}
|
||||||
show_call_element: showCall
|
}
|
||||||
|
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) {
|
_repositionTooltip: function(e) {
|
||||||
|
@ -176,23 +196,4 @@ module.exports = {
|
||||||
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px";
|
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.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 (
|
|
||||||
<RoomTile
|
|
||||||
room={room}
|
|
||||||
key={room.roomId}
|
|
||||||
collapsed={self.props.collapsed}
|
|
||||||
selected={selected}
|
|
||||||
unread={self.state.activityMap[room.roomId] === 1}
|
|
||||||
highlight={self.state.activityMap[room.roomId] === 2}
|
|
||||||
isInvite={isInvite}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
25
src/skins/vector/css/atoms/Spinner.css
Normal file
25
src/skins/vector/css/atoms/Spinner.css
Normal file
|
@ -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%;
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
.mx_RoomDropTarget,
|
|
||||||
.mx_RoomSettings_encrypt,
|
.mx_RoomSettings_encrypt,
|
||||||
.mx_CreateRoom_encrypt,
|
.mx_CreateRoom_encrypt,
|
||||||
.mx_RightPanel_filebutton
|
.mx_RightPanel_filebutton
|
||||||
|
|
|
@ -18,13 +18,13 @@ limitations under the License.
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
margin-left: 56px;
|
margin-left: 65px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
margin-left: -64px;
|
margin-left: -73px;
|
||||||
margin-top: -4px;
|
margin-top: -4px;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ limitations under the License.
|
||||||
.mx_MessageComposer .mx_MessageComposer_avatar {
|
.mx_MessageComposer .mx_MessageComposer_avatar {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 20px;
|
padding-right: 28px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,46 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomDropTarget {
|
.mx_RoomDropTarget {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: center;
|
margin-left: 10px;
|
||||||
margin-left: 8px;
|
margin-right: 15px;
|
||||||
margin-right: 8px;
|
padding-top: 5px;
|
||||||
padding-top: 16px;
|
padding-bottom: 5px;
|
||||||
padding-bottom: 16px;
|
border: 1px dashed #76cfa6;
|
||||||
background-color: #fbfbfb;
|
color: #454545;
|
||||||
border: 1px dashed #d7d7d7;
|
background-color: rgba(255,255,255,0.5);
|
||||||
border-radius: 8px;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ limitations under the License.
|
||||||
.mx_RoomHeader_leftRow {
|
.mx_RoomHeader_leftRow {
|
||||||
height: 48px;
|
height: 48px;
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
|
margin-left: -2px;
|
||||||
|
|
||||||
-webkit-box-ordinal-group: 1;
|
-webkit-box-ordinal-group: 1;
|
||||||
-moz-box-ordinal-group: 1;
|
-moz-box-ordinal-group: 1;
|
||||||
|
@ -103,7 +104,7 @@ limitations under the License.
|
||||||
color: #454545;
|
color: #454545;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
padding-left: 8px;
|
padding-left: 19px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +154,7 @@ limitations under the License.
|
||||||
max-height: 38px;
|
max-height: 38px;
|
||||||
color: #454545;
|
color: #454545;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
padding-left: 8px;
|
padding-left: 19px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
|
@ -16,13 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomTile {
|
.mx_RoomTile {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: table-row;
|
/* This fixes wrapping of long room names, but breaks drag & drop previews */
|
||||||
|
/* display: table-row; */
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile_avatar {
|
.mx_RoomTile_avatar {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
background: #eaf5f0;
|
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
|
@ -39,17 +39,16 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomTile_name {
|
.mx_RoomTile_name {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
|
width: 100%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
color: #454545;
|
color: rgba(69, 69, 69, 0.8);
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile_invite {
|
.mx_RoomTile_invite {
|
||||||
opacity: 0.5;
|
color: rgba(69, 69, 69, 0.5);
|
||||||
font-weight: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed .mx_RoomTile_name {
|
.collapsed .mx_RoomTile_name {
|
||||||
|
@ -106,15 +105,16 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomTile_unread,
|
.mx_RoomTile_unread,
|
||||||
.mx_RoomTile_highlight,
|
.mx_RoomTile_highlight,
|
||||||
.mx_RoomTile_invited
|
.mx_RoomTile_selected
|
||||||
{
|
{
|
||||||
font-weight: bold;
|
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: url('img/selected.png');
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right center;
|
background-position: right center;
|
||||||
|
|
|
@ -21,7 +21,6 @@ limitations under the License.
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
margin-top: 6px;
|
|
||||||
left: 64px;
|
left: 64px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,10 @@ limitations under the License.
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel_callView {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.mx_LeftPanel .mx_RoomList {
|
.mx_LeftPanel .mx_RoomList {
|
||||||
-webkit-box-ordinal-group: 1;
|
-webkit-box-ordinal-group: 1;
|
||||||
-moz-box-ordinal-group: 1;
|
-moz-box-ordinal-group: 1;
|
||||||
|
@ -53,8 +57,10 @@ limitations under the License.
|
||||||
-webkit-order: 3;
|
-webkit-order: 3;
|
||||||
order: 3;
|
order: 3;
|
||||||
|
|
||||||
-webkit-flex: 0 0 126px;
|
-webkit-flex: 0 0 140px;
|
||||||
flex: 0 0 126px;
|
flex: 0 0 140px;
|
||||||
|
|
||||||
|
background-color: rgba(118,207,166,0.19);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
|
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
|
||||||
|
@ -62,7 +68,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
|
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
|
||||||
margin-top: 12px;
|
margin-top: 17px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomList {
|
.mx_RoomList {
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
}
|
padding-bottom: 12px;
|
||||||
|
|
||||||
.mx_RoomList_invites,
|
|
||||||
.mx_RoomList_recents {
|
|
||||||
display: table;
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomList_expandButton {
|
.mx_RoomList_expandButton {
|
||||||
|
@ -31,14 +25,3 @@ limitations under the License.
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
padding-right: 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;
|
|
||||||
}
|
|
||||||
|
|
45
src/skins/vector/css/organisms/RoomSubList.css
Normal file
45
src/skins/vector/css/organisms/RoomSubList.css
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -129,7 +129,7 @@ limitations under the License.
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
margin-left: 54px;
|
margin-left: 63px;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomView_statusAreaBox_line {
|
.mx_RoomView_statusAreaBox_line {
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
margin-left: 54px;
|
margin-left: 63px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,14 +216,14 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomView_typingBar {
|
.mx_RoomView_typingBar {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-left: 54px;
|
margin-left: 63px;
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_typingImage {
|
.mx_RoomView_typingImage {
|
||||||
display: inline;
|
display: inline;
|
||||||
margin-left: -38px;
|
margin-left: -47px;
|
||||||
margin-top: -4px;
|
margin-top: -4px;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomView_uploadProgressOuter {
|
.mx_RoomView_uploadProgressOuter {
|
||||||
height: 4px;
|
height: 4px;
|
||||||
margin-left: 54px;
|
margin-left: 63px;
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomView_uploadFilename {
|
.mx_RoomView_uploadFilename {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-left: 56px;
|
margin-left: 65px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
color: #4a4a4a;
|
color: #4a4a4a;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,8 @@ limitations under the License.
|
||||||
|
|
||||||
background-color: #eaf5f0;
|
background-color: #eaf5f0;
|
||||||
|
|
||||||
-webkit-flex: 0 0 230px;
|
-webkit-flex: 0 0 210px;
|
||||||
flex: 0 0 230px;
|
flex: 0 0 210px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MatrixChat .mx_LeftPanel.collapsed {
|
.mx_MatrixChat .mx_LeftPanel.collapsed {
|
||||||
|
@ -87,8 +87,8 @@ limitations under the License.
|
||||||
-webkit-order: 2;
|
-webkit-order: 2;
|
||||||
order: 2;
|
order: 2;
|
||||||
|
|
||||||
padding-left: 12px;
|
padding-left: 25px;
|
||||||
padding-right: 12px;
|
padding-right: 22px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
-webkit-flex: 1;
|
-webkit-flex: 1;
|
||||||
|
@ -116,8 +116,8 @@ limitations under the License.
|
||||||
-webkit-order: 3;
|
-webkit-order: 3;
|
||||||
order: 3;
|
order: 3;
|
||||||
|
|
||||||
-webkit-flex: 0 0 230px;
|
-webkit-flex: 0 0 235px;
|
||||||
flex: 0 0 230px;
|
flex: 0 0 235px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MatrixChat .mx_RightPanel.collapsed {
|
.mx_MatrixChat .mx_RightPanel.collapsed {
|
||||||
|
|
BIN
src/skins/vector/img/list-close.png
Normal file
BIN
src/skins/vector/img/list-close.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
BIN
src/skins/vector/img/list-open.png
Normal file
BIN
src/skins/vector/img/list-open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1 KiB |
|
@ -30,6 +30,7 @@ skin['atoms.LogoutButton'] = require('./views/atoms/LogoutButton');
|
||||||
skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar');
|
skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar');
|
||||||
skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp');
|
skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp');
|
||||||
skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar');
|
skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar');
|
||||||
|
skin['atoms.Spinner'] = require('./views/atoms/Spinner');
|
||||||
skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton');
|
skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton');
|
||||||
skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets');
|
skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets');
|
||||||
skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias');
|
skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias');
|
||||||
|
@ -80,9 +81,11 @@ skin['organisms.QuestionDialog'] = require('./views/organisms/QuestionDialog');
|
||||||
skin['organisms.RightPanel'] = require('./views/organisms/RightPanel');
|
skin['organisms.RightPanel'] = require('./views/organisms/RightPanel');
|
||||||
skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory');
|
skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory');
|
||||||
skin['organisms.RoomList'] = require('./views/organisms/RoomList');
|
skin['organisms.RoomList'] = require('./views/organisms/RoomList');
|
||||||
|
skin['organisms.RoomSubList'] = require('./views/organisms/RoomSubList');
|
||||||
skin['organisms.RoomView'] = require('./views/organisms/RoomView');
|
skin['organisms.RoomView'] = require('./views/organisms/RoomView');
|
||||||
skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
|
skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
|
||||||
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');
|
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');
|
||||||
|
skin['pages.CompatibilityPage'] = require('./views/pages/CompatibilityPage');
|
||||||
skin['pages.MatrixChat'] = require('./views/pages/MatrixChat');
|
skin['pages.MatrixChat'] = require('./views/pages/MatrixChat');
|
||||||
skin['templates.Login'] = require('./views/templates/Login');
|
skin['templates.Login'] = require('./views/templates/Login');
|
||||||
skin['templates.Register'] = require('./views/templates/Register');
|
skin['templates.Register'] = require('./views/templates/Register');
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports = React.createClass({
|
||||||
var h = this.props.h || 32;
|
var h = this.props.h || 32;
|
||||||
var imgClass = this.props.imgClassName || "";
|
var imgClass = this.props.imgClassName || "";
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="mx_Spinner">
|
||||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,16 +18,25 @@ limitations under the License.
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
|
||||||
//var RoomDropTargetController = require('matrix-react-sdk/lib/controllers/molecules/RoomDropTargetController')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomDropTarget',
|
displayName: 'RoomDropTarget',
|
||||||
// mixins: [RoomDropTargetController],
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
if (this.props.placeholder) {
|
||||||
<div className="mx_RoomDropTarget">
|
return (
|
||||||
{this.props.text}
|
<div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder">
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<div className="mx_RoomDropTarget">
|
||||||
|
<div className="mx_RoomDropTarget_avatar"></div>
|
||||||
|
<div className="mx_RoomDropTarget_label">
|
||||||
|
{ this.props.label }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var DragSource = require('react-dnd').DragSource;
|
||||||
|
var DropTarget = require('react-dnd').DropTarget;
|
||||||
var classNames = require('classnames');
|
var classNames = require('classnames');
|
||||||
|
|
||||||
var RoomTileController = require('matrix-react-sdk/lib/controllers/molecules/RoomTile')
|
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')
|
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',
|
displayName: 'RoomTile',
|
||||||
mixins: [RoomTileController],
|
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() {
|
getInitialState: function() {
|
||||||
return( { hover : false });
|
return( { hover : false });
|
||||||
},
|
},
|
||||||
|
@ -42,18 +212,28 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
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 <RoomDropTarget placeholder={true}/>;
|
||||||
|
}
|
||||||
|
|
||||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
var me = this.props.room.currentState.members[myUserId];
|
||||||
var classes = classNames({
|
var classes = classNames({
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile': true,
|
||||||
'mx_RoomTile_selected': this.props.selected,
|
'mx_RoomTile_selected': this.props.selected,
|
||||||
'mx_RoomTile_unread': this.props.unread,
|
'mx_RoomTile_unread': this.props.unread,
|
||||||
'mx_RoomTile_highlight': this.props.highlight,
|
'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;
|
var name;
|
||||||
if (this.props.isInvite) {
|
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 {
|
else {
|
||||||
// XXX: We should never display raw room IDs, but sometimes the room name js sdk gives is undefined
|
// XXX: We should never display raw room IDs, but sometimes the room name js sdk gives is undefined
|
||||||
|
@ -92,7 +272,14 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
|
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(
|
||||||
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<div className="mx_RoomTile_avatar">
|
<div className="mx_RoomTile_avatar">
|
||||||
<RoomAvatar room={this.props.room} width="24" height="24" />
|
<RoomAvatar room={this.props.room} width="24" height="24" />
|
||||||
|
@ -100,6 +287,27 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
{ label }
|
{ label }
|
||||||
</div>
|
</div>
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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));
|
|
@ -34,7 +34,7 @@ module.exports = React.createClass({
|
||||||
render: function(){
|
render: function(){
|
||||||
var VideoView = sdk.getComponent('molecules.voip.VideoView');
|
var VideoView = sdk.getComponent('molecules.voip.VideoView');
|
||||||
return (
|
return (
|
||||||
<VideoView ref="video"/>
|
<VideoView ref="video" onClick={ this.props.onClick }/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,7 +78,7 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
|
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
|
||||||
return (
|
return (
|
||||||
<div className="mx_VideoView" ref={this.setContainer}>
|
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
|
||||||
<div className="mx_VideoView_remoteVideoFeed">
|
<div className="mx_VideoView_remoteVideoFeed">
|
||||||
<VideoFeed ref="remote"/>
|
<VideoFeed ref="remote"/>
|
||||||
<audio ref="remoteAudio"/>
|
<audio ref="remoteAudio"/>
|
||||||
|
|
|
@ -17,18 +17,72 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var DragDropContext = require('react-dnd').DragDropContext;
|
||||||
|
var HTML5Backend = require('react-dnd/modules/backends/HTML5');
|
||||||
var sdk = require('matrix-react-sdk')
|
var sdk = require('matrix-react-sdk')
|
||||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
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',
|
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() {
|
onHideClick: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'hide_left_panel',
|
action: 'hide_left_panel',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onCallViewClick: function() {
|
||||||
|
var call = CallHandler.getAnyActiveCall();
|
||||||
|
if (call) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: call.roomId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var RoomList = sdk.getComponent('organisms.RoomList');
|
var RoomList = sdk.getComponent('organisms.RoomList');
|
||||||
var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu');
|
var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu');
|
||||||
|
@ -44,10 +98,17 @@ module.exports = React.createClass({
|
||||||
// collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/>
|
// collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var callPreview;
|
||||||
|
if (this.state.showCallElement) {
|
||||||
|
var CallView = sdk.getComponent('molecules.voip.CallView');
|
||||||
|
callPreview = <CallView className="mx_LeftPanel_callView" onClick={this.onCallViewClick} />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={classes}>
|
<aside className={classes}>
|
||||||
{ collapseButton }
|
{ collapseButton }
|
||||||
<IncomingCallBox />
|
<IncomingCallBox />
|
||||||
|
{ callPreview }
|
||||||
<RoomList selectedRoom={this.props.selectedRoom} collapsed={this.props.collapsed}/>
|
<RoomList selectedRoom={this.props.selectedRoom} collapsed={this.props.collapsed}/>
|
||||||
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -55,3 +116,4 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports = DragDropContext(HTML5Backend)(LeftPanel);
|
||||||
|
|
|
@ -33,47 +33,79 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var CallView = sdk.getComponent('molecules.voip.CallView');
|
|
||||||
var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
|
|
||||||
|
|
||||||
var callElement;
|
|
||||||
if (this.state.show_call_element) {
|
|
||||||
callElement = <CallView className="mx_MatrixChat_callView"/>
|
|
||||||
}
|
|
||||||
|
|
||||||
var expandButton = this.props.collapsed ?
|
var expandButton = this.props.collapsed ?
|
||||||
<img className="mx_RoomList_expandButton" onClick={ this.onShowClick } src="img/menu.png" width="20" alt=">"/> :
|
<img className="mx_RoomList_expandButton" onClick={ this.onShowClick } src="img/menu.png" width="20" alt=">"/> :
|
||||||
null;
|
null;
|
||||||
|
|
||||||
var invitesLabel = this.props.collapsed ? null : "Invites";
|
var RoomSubList = sdk.getComponent('organisms.RoomSubList');
|
||||||
var recentsLabel = this.props.collapsed ? null : "Recent";
|
var self = this;
|
||||||
|
|
||||||
var invites;
|
|
||||||
if (this.state.inviteList.length) {
|
|
||||||
invites = <div>
|
|
||||||
<h2 className="mx_RoomList_invitesLabel">{ invitesLabel }</h2>
|
|
||||||
<div className="mx_RoomList_invites">
|
|
||||||
{this.makeRoomTiles(this.state.inviteList, true)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomList" onScroll={this._repositionTooltip}>
|
<div className="mx_RoomList" onScroll={self._repositionTooltip}>
|
||||||
{ expandButton }
|
{ expandButton }
|
||||||
{ callElement }
|
|
||||||
<h2 className="mx_RoomList_favouritesLabel">Favourites</h2>
|
|
||||||
<RoomDropTarget text="Drop here to favourite"/>
|
|
||||||
|
|
||||||
{ invites }
|
<RoomSubList list={ self.state.lists['m.invite'] }
|
||||||
|
label="Invites"
|
||||||
|
editable={ false }
|
||||||
|
order="recent"
|
||||||
|
activityMap={ self.state.activityMap }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed } />
|
||||||
|
|
||||||
<h2 className="mx_RoomList_recentsLabel">{ recentsLabel }</h2>
|
<RoomSubList list={ self.state.lists['m.favourite'] }
|
||||||
<div className="mx_RoomList_recents">
|
label="Favourites"
|
||||||
{this.makeRoomTiles(this.state.roomList, false)}
|
tagName="m.favourite"
|
||||||
</div>
|
verb="favourite"
|
||||||
|
editable={ true }
|
||||||
|
order="manual"
|
||||||
|
activityMap={ self.state.activityMap }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed } />
|
||||||
|
|
||||||
<h2 className="mx_RoomList_archiveLabel">Archive</h2>
|
<RoomSubList list={ self.state.lists['m.recent'] }
|
||||||
<RoomDropTarget text="Drop here to archive"/>
|
label="Conversations"
|
||||||
|
editable={ true }
|
||||||
|
verb="restore"
|
||||||
|
order="recent"
|
||||||
|
activityMap={ self.state.activityMap }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed } />
|
||||||
|
|
||||||
|
{ Object.keys(self.state.lists).map(function(tagName) {
|
||||||
|
if (!tagName.match(/^m\.(invite|favourite|recent|lowpriority|archived)$/)) {
|
||||||
|
return <RoomSubList list={ self.state.lists[tagName] }
|
||||||
|
key={ tagName }
|
||||||
|
label={ tagName }
|
||||||
|
tagName={ tagName }
|
||||||
|
verb={ "tag as " + tagName }
|
||||||
|
editable={ true }
|
||||||
|
order="manual"
|
||||||
|
activityMap={ self.state.activityMap }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed } />
|
||||||
|
|
||||||
|
}
|
||||||
|
}) }
|
||||||
|
|
||||||
|
<RoomSubList list={ self.state.lists['m.lowpriority'] }
|
||||||
|
label="Low priority"
|
||||||
|
tagName="m.lowpriority"
|
||||||
|
verb="demote"
|
||||||
|
editable={ true }
|
||||||
|
order="recent"
|
||||||
|
bottommost={ self.state.lists['m.archived'].length === 0 }
|
||||||
|
activityMap={ self.state.activityMap }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed } />
|
||||||
|
|
||||||
|
<RoomSubList list={ self.state.lists['m.archived'] }
|
||||||
|
label="Historical"
|
||||||
|
editable={ false }
|
||||||
|
order="recent"
|
||||||
|
bottommost={ true }
|
||||||
|
activityMap={ self.state.activityMap }
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
290
src/skins/vector/views/organisms/RoomSubList.js
Normal file
290
src/skins/vector/views/organisms/RoomSubList.js
Normal file
|
@ -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 (
|
||||||
|
<RoomTile
|
||||||
|
room={ room }
|
||||||
|
roomSubList={ self }
|
||||||
|
key={ room.roomId }
|
||||||
|
collapsed={ self.props.collapsed }
|
||||||
|
selected={ selected }
|
||||||
|
unread={ self.props.activityMap[room.roomId] === 1 }
|
||||||
|
highlight={ self.props.activityMap[room.roomId] === 2 }
|
||||||
|
isInvite={ self.props.label === 'Invites' } />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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 = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = <div className={ classes }>
|
||||||
|
{ target }
|
||||||
|
{ this.makeRoomTiles() }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subList = <div className={ classes }>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectDropTarget(
|
||||||
|
<div>
|
||||||
|
<h2 onClick={ this.onClick } className="mx_RoomSubList_label">{ this.props.collapsed ? '' : this.props.label }
|
||||||
|
<img className="mx_RoomSubList_chevron" src={ this.state.hidden ? "/img/list-open.png" : "/img/list-close.png" } width="10" height="10"/>
|
||||||
|
</h2>
|
||||||
|
{ subList }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<div className="mx_RoomSubList">
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
|
@ -196,6 +196,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var typingString = this.getWhoIsTypingString();
|
var typingString = this.getWhoIsTypingString();
|
||||||
|
//typingString = "Testing typing...";
|
||||||
var unreadMsgs = this.getUnreadMessagesString();
|
var unreadMsgs = this.getUnreadMessagesString();
|
||||||
// no conn bar trumps unread count since you can't get unread messages
|
// no conn bar trumps unread count since you can't get unread messages
|
||||||
// without a connection! (technically may already have some but meh)
|
// without a connection! (technically may already have some but meh)
|
||||||
|
|
|
@ -21,9 +21,6 @@ var sdk = require('matrix-react-sdk')
|
||||||
|
|
||||||
var MatrixChatController = require('matrix-react-sdk/lib/controllers/pages/MatrixChat')
|
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 dis = require('matrix-react-sdk/lib/dispatcher');
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
var ContextualMenu = require("../../../../ContextualMenu");
|
var ContextualMenu = require("../../../../ContextualMenu");
|
||||||
|
@ -154,8 +151,9 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (this.state.logged_in) {
|
} else if (this.state.logged_in) {
|
||||||
|
var Spinner = sdk.getComponent('atoms.Spinner');
|
||||||
return (
|
return (
|
||||||
<Loader />
|
<Spinner />
|
||||||
);
|
);
|
||||||
} else if (this.state.screen == 'register') {
|
} else if (this.state.screen == 'register') {
|
||||||
return (
|
return (
|
||||||
|
@ -171,4 +169,4 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
Loading…
Reference in a new issue