mirror of
https://github.com/SchildiChat/element-web.git
synced 2024-10-01 01:26:12 -04:00
Merge pull request #342 from vector-im/matthew/orderable-roomlist
Implement reorderable rooms via room tagging.
This commit is contained in:
commit
6d9817e5e7
@ -34,7 +34,8 @@
|
||||
"q": "^1.4.1",
|
||||
"react": "^0.13.3",
|
||||
"react-loader": "^1.4.0",
|
||||
"sanitize-html": "^1.11.1"
|
||||
"react-dnd": "^1.1.8",
|
||||
"sanitize-html": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^5.8.23",
|
||||
|
@ -23,16 +23,23 @@ 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 +54,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 +74,6 @@ module.exports = {
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
this.state.activityMap[newProps.selectedRoom] = undefined;
|
||||
this._recheckCallElement(newProps.selectedRoom);
|
||||
this.setState({
|
||||
activityMap: this.state.activityMap
|
||||
});
|
||||
@ -109,6 +110,10 @@ module.exports = {
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
onRoomTags: function(event, room) {
|
||||
this.refreshRoomList();
|
||||
},
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
setTimeout(this.refreshRoomList, 0);
|
||||
},
|
||||
@ -117,26 +122,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,23 +166,28 @@ 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) {
|
||||
@ -176,23 +196,4 @@ module.exports = {
|
||||
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_CreateRoom_encrypt,
|
||||
.mx_RightPanel_filebutton
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -21,7 +21,6 @@ limitations under the License.
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
margin-top: 6px;
|
||||
left: 64px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
@ -34,6 +34,10 @@ limitations under the License.
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_callView {
|
||||
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_RoomList {
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-moz-box-ordinal-group: 1;
|
||||
@ -53,8 +57,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 +68,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
|
||||
margin-top: 12px;
|
||||
margin-top: 17px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
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;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
@ -170,7 +170,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomView_statusAreaBox_line {
|
||||
border-top: 1px solid #eee;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
@ -216,14 +216,14 @@ limitations under the License.
|
||||
|
||||
.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;
|
||||
}
|
||||
@ -243,7 +243,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomView_uploadProgressOuter {
|
||||
height: 4px;
|
||||
margin-left: 54px;
|
||||
margin-left: 63px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
@ -254,7 +254,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomView_uploadFilename {
|
||||
margin-top: 5px;
|
||||
margin-left: 56px;
|
||||
margin-left: 65px;
|
||||
opacity: 0.5;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
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.0 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.0 KiB |
@ -30,6 +30,7 @@ skin['atoms.LogoutButton'] = require('./views/atoms/LogoutButton');
|
||||
skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar');
|
||||
skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp');
|
||||
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.Presets'] = require('./views/atoms/create_room/Presets');
|
||||
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.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');
|
||||
skin['pages.CompatibilityPage'] = require('./views/pages/CompatibilityPage');
|
||||
skin['pages.MatrixChat'] = require('./views/pages/MatrixChat');
|
||||
skin['templates.Login'] = require('./views/templates/Login');
|
||||
skin['templates.Register'] = require('./views/templates/Register');
|
||||
|
@ -26,7 +26,7 @@ module.exports = React.createClass({
|
||||
var h = this.props.h || 32;
|
||||
var imgClass = this.props.imgClassName || "";
|
||||
return (
|
||||
<div>
|
||||
<div className="mx_Spinner">
|
||||
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -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 (
|
||||
<div className="mx_RoomDropTarget">
|
||||
{this.props.text}
|
||||
</div>
|
||||
);
|
||||
if (this.props.placeholder) {
|
||||
return (
|
||||
<div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder">
|
||||
</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';
|
||||
|
||||
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,18 +212,28 @@ 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 <RoomDropTarget placeholder={true}/>;
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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');
|
||||
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="mx_RoomTile_avatar">
|
||||
<RoomAvatar room={this.props.room} width="24" height="24" />
|
||||
@ -100,6 +287,27 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
{ label }
|
||||
</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(){
|
||||
var VideoView = sdk.getComponent('molecules.voip.VideoView');
|
||||
return (
|
||||
<VideoView ref="video"/>
|
||||
<VideoView ref="video" onClick={ this.props.onClick }/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -78,7 +78,7 @@ module.exports = React.createClass({
|
||||
render: function() {
|
||||
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
|
||||
return (
|
||||
<div className="mx_VideoView" ref={this.setContainer}>
|
||||
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
|
||||
<div className="mx_VideoView_remoteVideoFeed">
|
||||
<VideoFeed ref="remote"/>
|
||||
<audio ref="remoteAudio"/>
|
||||
|
@ -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/modules/backends/HTML5');
|
||||
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 = <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 (
|
||||
<aside className={classes}>
|
||||
{ collapseButton }
|
||||
<IncomingCallBox />
|
||||
{ callPreview }
|
||||
<RoomList selectedRoom={this.props.selectedRoom} collapsed={this.props.collapsed}/>
|
||||
<BottomLeftMenu collapsed={this.props.collapsed}/>
|
||||
</aside>
|
||||
@ -55,3 +116,4 @@ module.exports = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = DragDropContext(HTML5Backend)(LeftPanel);
|
||||
|
@ -33,47 +33,79 @@ 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 = <CallView className="mx_MatrixChat_callView"/>
|
||||
}
|
||||
|
||||
var expandButton = this.props.collapsed ?
|
||||
<img className="mx_RoomList_expandButton" onClick={ this.onShowClick } src="img/menu.png" width="20" alt=">"/> :
|
||||
null;
|
||||
|
||||
var invitesLabel = this.props.collapsed ? null : "Invites";
|
||||
var recentsLabel = this.props.collapsed ? null : "Recent";
|
||||
|
||||
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>
|
||||
}
|
||||
var RoomSubList = sdk.getComponent('organisms.RoomSubList');
|
||||
var self = this;
|
||||
|
||||
return (
|
||||
<div className="mx_RoomList" onScroll={this._repositionTooltip}>
|
||||
<div className="mx_RoomList" onScroll={self._repositionTooltip}>
|
||||
{ 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>
|
||||
<div className="mx_RoomList_recents">
|
||||
{this.makeRoomTiles(this.state.roomList, false)}
|
||||
</div>
|
||||
<RoomSubList list={ self.state.lists['m.favourite'] }
|
||||
label="Favourites"
|
||||
tagName="m.favourite"
|
||||
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>
|
||||
<RoomDropTarget text="Drop here to archive"/>
|
||||
<RoomSubList list={ self.state.lists['m.recent'] }
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
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 {
|
||||
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)
|
||||
|
@ -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 (
|
||||
<Loader />
|
||||
<Spinner />
|
||||
);
|
||||
} else if (this.state.screen == 'register') {
|
||||
return (
|
||||
@ -171,4 +169,4 @@ module.exports = React.createClass({
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user