2015-11-03 19:19:37 -05:00
|
|
|
/*
|
2016-01-06 23:17:56 -05:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2015-11-03 19:19:37 -05:00
|
|
|
|
|
|
|
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');
|
2016-08-26 09:35:40 -04:00
|
|
|
var ReactDOM = require('react-dom');
|
2016-08-22 09:10:06 -04:00
|
|
|
var classNames = require('classnames');
|
2015-11-03 21:25:08 -05:00
|
|
|
var DropTarget = require('react-dnd').DropTarget;
|
2015-11-03 19:19:37 -05:00
|
|
|
var sdk = require('matrix-react-sdk')
|
|
|
|
var dis = require('matrix-react-sdk/lib/dispatcher');
|
2016-01-06 13:29:27 -05:00
|
|
|
var Unread = require('matrix-react-sdk/lib/Unread');
|
2016-02-18 21:09:04 -05:00
|
|
|
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
2016-08-23 06:14:45 -04:00
|
|
|
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
|
2016-09-16 21:39:31 -04:00
|
|
|
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
|
2017-01-05 18:37:12 -05:00
|
|
|
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
|
2017-04-17 15:53:46 -04:00
|
|
|
var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
|
2017-04-17 21:43:06 -04:00
|
|
|
var RoomSubListHeader = require('./RoomSubListHeader.js');
|
2015-11-03 19:19:37 -05:00
|
|
|
|
2017-04-16 10:58:00 -04:00
|
|
|
// turn this on for drag & drop console debugging galore
|
2015-11-08 07:24:32 -05:00
|
|
|
var debug = false;
|
|
|
|
|
2016-08-30 07:14:32 -04:00
|
|
|
const TRUNCATE_AT = 10;
|
2016-08-22 10:50:36 -04:00
|
|
|
|
2015-11-03 21:25:08 -05:00
|
|
|
var roomListTarget = {
|
|
|
|
canDrop: function() {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2015-11-06 21:57:56 -05:00
|
|
|
drop: function(props, monitor, component) {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("dropped on sublist")
|
2015-11-06 21:57:56 -05:00
|
|
|
},
|
|
|
|
|
2015-11-03 21:25:08 -05:00
|
|
|
hover: function(props, monitor, component) {
|
|
|
|
var item = monitor.getItem();
|
|
|
|
|
|
|
|
if (component.state.sortedList.length == 0 && props.editable) {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("hovering on sublist " + props.label + ", isOver=" + monitor.isOver());
|
2015-11-03 21:25:08 -05:00
|
|
|
|
|
|
|
if (item.targetList !== component) {
|
|
|
|
item.targetList.removeRoomTile(item.room);
|
|
|
|
item.targetList = component;
|
|
|
|
}
|
|
|
|
|
|
|
|
component.moveRoomTile(item.room, 0);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var RoomSubList = React.createClass({
|
2015-11-03 19:19:37 -05:00
|
|
|
displayName: 'RoomSubList',
|
|
|
|
|
2015-11-08 07:24:32 -05:00
|
|
|
debug: debug,
|
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
propTypes: {
|
|
|
|
list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
|
|
|
|
label: React.PropTypes.string.isRequired,
|
2015-11-06 14:54:07 -05:00
|
|
|
tagName: React.PropTypes.string,
|
2015-11-03 19:19:37 -05:00
|
|
|
editable: React.PropTypes.bool,
|
2016-08-01 08:44:04 -04:00
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
order: React.PropTypes.string.isRequired,
|
2016-08-01 08:44:04 -04:00
|
|
|
|
2015-12-18 10:17:18 -05:00
|
|
|
startAsHidden: React.PropTypes.bool,
|
|
|
|
showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded
|
2016-04-15 12:54:48 -04:00
|
|
|
collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
|
2015-12-18 06:56:22 -05:00
|
|
|
onHeaderClick: React.PropTypes.func,
|
2015-12-18 12:00:20 -05:00
|
|
|
alwaysShowHeader: React.PropTypes.bool,
|
2017-04-19 20:13:13 -04:00
|
|
|
selectedRoom: React.PropTypes.string,
|
2016-01-22 10:46:58 -05:00
|
|
|
incomingCall: React.PropTypes.object,
|
2016-04-15 12:54:48 -04:00
|
|
|
onShowMoreRooms: React.PropTypes.func,
|
|
|
|
searchFilter: React.PropTypes.string,
|
2015-11-03 19:19:37 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return {
|
2015-12-18 10:17:18 -05:00
|
|
|
hidden: this.props.startAsHidden || false,
|
2016-09-15 12:31:15 -04:00
|
|
|
truncateAt: TRUNCATE_AT,
|
2015-11-03 19:19:37 -05:00
|
|
|
sortedList: [],
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-12-18 06:56:22 -05:00
|
|
|
getDefaultProps: function() {
|
|
|
|
return {
|
2016-01-22 10:46:58 -05:00
|
|
|
onHeaderClick: function() {}, // NOP
|
|
|
|
onShowMoreRooms: function() {} // NOP
|
2015-12-18 06:56:22 -05:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
componentWillMount: function() {
|
2017-04-17 15:53:46 -04:00
|
|
|
constantTimeDispatcher.register("RoomSubList.sort", this.props.tagName, this.onSort);
|
2017-04-18 14:27:57 -04:00
|
|
|
constantTimeDispatcher.register("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
|
2016-04-15 12:54:48 -04:00
|
|
|
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
|
2017-04-17 21:43:06 -04:00
|
|
|
this._fixUndefinedOrder(this.props.list);
|
2017-04-17 15:53:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
componentWillUnmount: function() {
|
|
|
|
constantTimeDispatcher.unregister("RoomSubList.sort", this.props.tagName, this.onSort);
|
2017-04-18 14:27:57 -04:00
|
|
|
constantTimeDispatcher.unregister("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
|
2015-11-03 19:19:37 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
componentWillReceiveProps: function(newProps) {
|
|
|
|
// order the room list appropriately before we re-render
|
2015-11-08 08:42:45 -05:00
|
|
|
//if (debug) console.log("received new props, list = " + newProps.list);
|
2016-04-15 12:54:48 -04:00
|
|
|
this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order);
|
2017-04-17 21:43:06 -04:00
|
|
|
this._fixUndefinedOrder(newProps.list);
|
2017-04-17 15:53:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
onSort: function() {
|
|
|
|
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
|
|
|
|
// we deliberately don't waste time trying to fix undefined ordering here
|
2016-04-15 12:54:48 -04:00
|
|
|
},
|
|
|
|
|
2017-04-18 14:27:57 -04:00
|
|
|
onRefresh: function() {
|
|
|
|
this.forceUpdate();
|
|
|
|
},
|
|
|
|
|
2016-04-15 12:54:48 -04:00
|
|
|
applySearchFilter: function(list, filter) {
|
|
|
|
if (filter === "") return list;
|
|
|
|
return list.filter((room) => {
|
|
|
|
return room.name && room.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0
|
|
|
|
});
|
2015-11-03 19:19:37 -05:00
|
|
|
},
|
|
|
|
|
2016-08-28 14:14:54 -04:00
|
|
|
// The header is collapsable if it is hidden or not stuck
|
2016-08-26 10:53:31 -04:00
|
|
|
// The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
|
2016-08-28 14:14:54 -04:00
|
|
|
isCollapsableOnClick: function() {
|
2017-04-17 21:43:06 -04:00
|
|
|
var stuck = this.refs.header.refs.header.dataset.stuck;
|
2016-08-28 14:14:54 -04:00
|
|
|
if (this.state.hidden || stuck === undefined || stuck === "none") {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2016-08-26 09:35:40 -04:00
|
|
|
},
|
|
|
|
|
2015-11-06 15:25:20 -05:00
|
|
|
onClick: function(ev) {
|
2016-08-28 14:14:54 -04:00
|
|
|
if (this.isCollapsableOnClick()) {
|
2016-09-15 12:17:45 -04:00
|
|
|
// The header isCollapsable, so the click is to be interpreted as collapse and truncation logic
|
|
|
|
var isHidden = !this.state.hidden;
|
|
|
|
this.setState({ hidden : isHidden });
|
|
|
|
|
|
|
|
if (isHidden) {
|
|
|
|
// as good a way as any to reset the truncate state
|
|
|
|
this.setState({ truncateAt : TRUNCATE_AT });
|
2016-08-26 09:35:40 -04:00
|
|
|
}
|
2016-08-22 10:50:36 -04:00
|
|
|
|
2016-08-26 09:35:40 -04:00
|
|
|
this.props.onShowMoreRooms();
|
2016-08-26 10:08:47 -04:00
|
|
|
this.props.onHeaderClick(isHidden);
|
|
|
|
} else {
|
2016-08-26 10:53:31 -04:00
|
|
|
// The header is stuck, so the click is to be interpreted as a scroll to the header
|
2017-04-17 21:43:06 -04:00
|
|
|
this.props.onHeaderClick(this.state.hidden, this.refs.header.refs.header.dataset.originalPosition);
|
2016-01-22 11:09:06 -05:00
|
|
|
}
|
2015-11-06 15:25:20 -05:00
|
|
|
},
|
|
|
|
|
2017-04-20 09:21:36 -04:00
|
|
|
onRoomTileClick(roomId, ev) {
|
2017-03-06 12:55:12 -05:00
|
|
|
dis.dispatch({
|
|
|
|
action: 'view_room',
|
|
|
|
room_id: roomId,
|
2017-04-20 09:21:36 -04:00
|
|
|
clear_search: (ev && (ev.keyCode == 13 || ev.keyCode == 32)),
|
2017-03-06 12:55:12 -05:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
tsOfNewestEvent: function(room) {
|
2015-12-04 11:21:42 -05:00
|
|
|
for (var i = room.timeline.length - 1; i >= 0; --i) {
|
|
|
|
var ev = room.timeline[i];
|
2017-03-29 09:29:41 -04:00
|
|
|
if (ev.getTs() &&
|
|
|
|
(Unread.eventTriggersUnreadCount(ev) ||
|
|
|
|
(ev.getSender() === MatrixClientPeg.get().credentials.userId))
|
|
|
|
) {
|
2015-12-04 11:21:42 -05:00
|
|
|
return ev.getTs();
|
|
|
|
}
|
2015-11-03 19:19:37 -05:00
|
|
|
}
|
2015-12-04 11:21:42 -05:00
|
|
|
|
|
|
|
// we might only have events that don't trigger the unread indicator,
|
|
|
|
// in which case use the oldest event even if normally it wouldn't count.
|
|
|
|
// This is better than just assuming the last event was forever ago.
|
2017-03-29 09:29:41 -04:00
|
|
|
if (room.timeline.length && room.timeline[0].getTs()) {
|
2015-12-04 11:21:42 -05:00
|
|
|
return room.timeline[0].getTs();
|
|
|
|
} else {
|
2015-11-03 19:19:37 -05:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2016-08-09 14:20:27 -04:00
|
|
|
lexicographicalComparator: function(roomA, roomB) {
|
|
|
|
return roomA.name > roomB.name ? 1 : -1;
|
|
|
|
},
|
|
|
|
|
|
|
|
// Generates the manual comparator using the given list
|
2016-08-10 06:39:10 -04:00
|
|
|
manualComparator: function(roomA, roomB) {
|
|
|
|
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
|
|
|
|
|
|
|
|
// Make sure the room tag has an order element, if not set it to be the bottom
|
|
|
|
var a = roomA.tags[this.props.tagName].order;
|
|
|
|
var b = roomB.tags[this.props.tagName].order;
|
|
|
|
|
|
|
|
// Order undefined room tag orders to the bottom
|
|
|
|
if (a === undefined && b !== undefined) {
|
|
|
|
return 1;
|
|
|
|
} else if (a !== undefined && b === undefined) {
|
|
|
|
return -1;
|
2016-08-09 14:20:27 -04:00
|
|
|
}
|
2016-08-10 06:39:10 -04:00
|
|
|
|
|
|
|
return a == b ? this.lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1);
|
2015-11-03 19:19:37 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
sortList: function(list, order) {
|
2015-11-06 17:30:57 -05:00
|
|
|
if (list === undefined) list = this.state.sortedList;
|
2015-11-05 06:21:45 -05:00
|
|
|
if (order === undefined) order = this.props.order;
|
2015-11-03 19:19:37 -05:00
|
|
|
var comparator;
|
|
|
|
list = list || [];
|
2016-08-10 06:39:10 -04:00
|
|
|
if (order === "manual") comparator = this.manualComparator;
|
2015-11-03 19:19:37 -05:00
|
|
|
if (order === "recent") comparator = this.recentsComparator;
|
|
|
|
|
2015-11-08 08:42:45 -05:00
|
|
|
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
|
2015-11-03 19:19:37 -05:00
|
|
|
this.setState({ sortedList: list.sort(comparator) });
|
|
|
|
},
|
|
|
|
|
2016-08-23 06:35:03 -04:00
|
|
|
_shouldShowNotifBadge: function(roomNotifState) {
|
|
|
|
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
|
|
|
|
return showBadgeInStates.indexOf(roomNotifState) > -1;
|
|
|
|
},
|
|
|
|
|
|
|
|
_shouldShowMentionBadge: function(roomNotifState) {
|
|
|
|
return roomNotifState != RoomNotifs.MUTE;
|
|
|
|
},
|
|
|
|
|
2016-09-16 08:25:39 -04:00
|
|
|
/**
|
|
|
|
* Total up all the notification counts from the rooms
|
|
|
|
*
|
|
|
|
* @param {Number} If supplied will only total notifications for rooms outside the truncation number
|
|
|
|
* @returns {Array} The array takes the form [total, highlight] where highlight is a bool
|
|
|
|
*/
|
|
|
|
roomNotificationCount: function(truncateAt) {
|
2016-08-23 05:47:17 -04:00
|
|
|
var self = this;
|
|
|
|
|
2016-09-16 08:25:39 -04:00
|
|
|
return this.props.list.reduce(function(result, room, index) {
|
|
|
|
if (truncateAt === undefined || index >= truncateAt) {
|
|
|
|
var roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
|
|
|
var highlight = room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites';
|
|
|
|
var notificationCount = room.getUnreadNotificationCount();
|
|
|
|
|
|
|
|
const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState);
|
|
|
|
const mentionBadges = highlight && self._shouldShowMentionBadge(roomNotifState);
|
|
|
|
const badges = notifBadges || mentionBadges;
|
|
|
|
|
|
|
|
if (badges) {
|
|
|
|
result[0] += notificationCount;
|
2016-08-23 05:47:17 -04:00
|
|
|
}
|
2017-04-15 07:02:16 -04:00
|
|
|
|
|
|
|
result[1] |= highlight;
|
2016-08-23 05:47:17 -04:00
|
|
|
}
|
2016-08-30 05:45:59 -04:00
|
|
|
return result;
|
|
|
|
}, [0, false]);
|
2016-08-23 05:47:17 -04:00
|
|
|
},
|
|
|
|
|
2016-08-23 07:40:15 -04:00
|
|
|
_updateSubListCount: function() {
|
2016-08-23 08:24:02 -04:00
|
|
|
// Force an update by setting the state to the current state
|
|
|
|
// Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
|
|
|
|
// method is honoured
|
|
|
|
this.setState(this.state);
|
2016-08-23 07:40:15 -04:00
|
|
|
},
|
|
|
|
|
2015-11-03 21:25:08 -05:00
|
|
|
moveRoomTile: function(room, atIndex) {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
|
2015-11-03 21:25:08 -05:00
|
|
|
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
|
|
|
|
var found = this.findRoomTile(room);
|
|
|
|
var rooms = this.state.sortedList;
|
|
|
|
if (found.room) {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("removing at index " + found.index + " and adding at index " + atIndex);
|
2015-11-03 21:25:08 -05:00
|
|
|
rooms.splice(found.index, 1);
|
|
|
|
rooms.splice(atIndex, 0, found.room);
|
|
|
|
}
|
|
|
|
else {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("Adding at index " + atIndex);
|
2015-11-03 21:25:08 -05:00
|
|
|
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) {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("remove room " + room.roomId);
|
2015-11-03 21:25:08 -05:00
|
|
|
var found = this.findRoomTile(room);
|
|
|
|
var rooms = this.state.sortedList;
|
|
|
|
if (found.room) {
|
|
|
|
rooms.splice(found.index, 1);
|
2016-03-15 14:38:24 -04:00
|
|
|
}
|
2015-11-03 21:25:08 -05:00
|
|
|
else {
|
2015-11-08 07:24:32 -05:00
|
|
|
console.warn("Can't remove room " + room.roomId + " - can't find it");
|
2015-11-03 21:25:08 -05:00
|
|
|
}
|
|
|
|
this.setState({ sortedList: rooms });
|
|
|
|
},
|
|
|
|
|
2016-03-15 14:38:24 -04:00
|
|
|
findRoomTile: function(room) {
|
|
|
|
var index = this.state.sortedList.indexOf(room);
|
2015-11-03 21:25:08 -05:00
|
|
|
if (index >= 0) {
|
2015-11-06 21:57:56 -05:00
|
|
|
// console.log("found: room: " + room.roomId + " with index " + index);
|
2015-11-03 21:25:08 -05:00
|
|
|
}
|
|
|
|
else {
|
2015-11-08 07:24:32 -05:00
|
|
|
if (debug) console.log("didn't find room");
|
2015-11-03 21:25:08 -05:00
|
|
|
room = null;
|
|
|
|
}
|
|
|
|
return ({
|
|
|
|
room: room,
|
|
|
|
index: index,
|
|
|
|
});
|
2015-11-06 14:54:07 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
calcManualOrderTagData: function(room) {
|
2016-03-15 14:38:24 -04:00
|
|
|
var index = this.state.sortedList.indexOf(room);
|
2015-11-06 14:54:07 -05:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
},
|
2015-11-03 21:25:08 -05:00
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
makeRoomTiles: function() {
|
|
|
|
var self = this;
|
2016-09-09 11:15:45 -04:00
|
|
|
var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile");
|
2015-11-03 19:19:37 -05:00
|
|
|
return this.state.sortedList.map(function(room) {
|
2015-11-03 21:25:08 -05:00
|
|
|
// XXX: is it evil to pass in self as a prop to RoomTile?
|
2015-11-03 19:19:37 -05:00
|
|
|
return (
|
2016-09-09 11:15:45 -04:00
|
|
|
<DNDRoomTile
|
2015-11-06 21:57:56 -05:00
|
|
|
room={ room }
|
|
|
|
roomSubList={ self }
|
|
|
|
key={ room.roomId }
|
2015-11-10 18:56:51 -05:00
|
|
|
collapsed={ self.props.collapsed || false}
|
2017-04-19 20:13:13 -04:00
|
|
|
selectedRoom={ self.props.selectedRoom }
|
2015-12-16 21:53:53 -05:00
|
|
|
isInvite={ self.props.label === 'Invites' }
|
2016-08-23 07:40:15 -04:00
|
|
|
refreshSubList={ self._updateSubListCount }
|
2017-03-06 12:55:12 -05:00
|
|
|
incomingCall={ null }
|
|
|
|
onClick={ self.onRoomTileClick }
|
|
|
|
/>
|
2015-11-03 19:19:37 -05:00
|
|
|
);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-09-15 12:17:45 -04:00
|
|
|
_createOverflowTile: function(overflowCount, totalCount) {
|
2016-09-16 06:34:16 -04:00
|
|
|
var content = <div className="mx_RoomSubList_chevronDown"></div>;
|
2016-09-16 08:25:39 -04:00
|
|
|
|
|
|
|
var overflowNotifications = this.roomNotificationCount(TRUNCATE_AT);
|
|
|
|
var overflowNotifCount = overflowNotifications[0];
|
|
|
|
var overflowNotifHighlight = overflowNotifications[1];
|
|
|
|
if (overflowNotifCount && !this.props.collapsed) {
|
2016-09-16 21:39:31 -04:00
|
|
|
content = FormattingUtils.formatCount(overflowNotifCount);
|
2016-09-16 08:25:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var badgeClasses = classNames({
|
|
|
|
'mx_RoomSubList_moreBadge': true,
|
|
|
|
'mx_RoomSubList_moreBadgeNotify': overflowNotifCount && !this.props.collapsed,
|
|
|
|
'mx_RoomSubList_moreBadgeHighlight': overflowNotifHighlight && !this.props.collapsed,
|
|
|
|
});
|
|
|
|
|
2016-09-15 12:17:45 -04:00
|
|
|
return (
|
2017-01-05 18:37:12 -05:00
|
|
|
<AccessibleButton className="mx_RoomSubList_ellipsis" onClick={this._showFullMemberList}>
|
2016-09-16 05:57:55 -04:00
|
|
|
<div className="mx_RoomSubList_line"></div>
|
|
|
|
<div className="mx_RoomSubList_more">more</div>
|
2017-01-05 18:37:12 -05:00
|
|
|
<div className={ badgeClasses }>{ content }</div>
|
|
|
|
</AccessibleButton>
|
2016-09-15 12:17:45 -04:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
_showFullMemberList: function() {
|
|
|
|
this.setState({
|
|
|
|
truncateAt: -1
|
|
|
|
});
|
|
|
|
|
|
|
|
this.props.onShowMoreRooms();
|
|
|
|
this.props.onHeaderClick(false);
|
|
|
|
},
|
2016-01-22 10:31:42 -05:00
|
|
|
|
2016-08-10 06:39:10 -04:00
|
|
|
// Fix any undefined order elements of a room in a manual ordered list
|
|
|
|
// room.tag[tagname].order
|
|
|
|
_fixUndefinedOrder: function(list) {
|
|
|
|
if (this.props.order === "manual") {
|
|
|
|
var order = 0.0;
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// Find the highest (lowest position) order of a room in a manual ordered list
|
|
|
|
list.forEach(function(room) {
|
|
|
|
if (room.tags.hasOwnProperty(self.props.tagName)) {
|
|
|
|
if (order < room.tags[self.props.tagName].order) {
|
|
|
|
order = room.tags[self.props.tagName].order;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Fix any undefined order elements of a room in a manual ordered list
|
|
|
|
// Do this one at a time, as each time a rooms tag data is updated the RoomList
|
|
|
|
// gets triggered and another list is passed in. Doing it one at a time means that
|
|
|
|
// we always correctly calculate the highest order for the list - stops multiple
|
|
|
|
// rooms getting the same order. This is only really relevant for the first time this
|
2017-04-16 10:58:00 -04:00
|
|
|
// is run with historical room tag data, after that there should only be one undefined
|
2016-08-10 06:39:10 -04:00
|
|
|
// in the list at a time anyway.
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
2016-08-26 18:49:07 -04:00
|
|
|
if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) {
|
2016-08-10 06:39:10 -04:00
|
|
|
MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() {
|
|
|
|
// Do any final stuff here
|
|
|
|
}).fail(function(err) {
|
|
|
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
2017-03-12 19:26:26 -04:00
|
|
|
console.error("Failed to add tag " + self.props.tagName + " to room" + err);
|
2016-08-10 06:39:10 -04:00
|
|
|
Modal.createDialog(ErrorDialog, {
|
2017-03-16 07:36:57 -04:00
|
|
|
title: "Error",
|
2017-03-12 19:26:26 -04:00
|
|
|
description: "Failed to add tag " + self.props.tagName + " to room",
|
2016-08-10 06:39:10 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
render: function() {
|
2015-11-03 21:25:08 -05:00
|
|
|
var connectDropTarget = this.props.connectDropTarget;
|
2015-12-01 06:19:54 -05:00
|
|
|
var RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
|
2016-01-22 10:31:42 -05:00
|
|
|
var TruncatedList = sdk.getComponent('elements.TruncatedList');
|
2015-11-03 19:19:37 -05:00
|
|
|
|
|
|
|
var label = this.props.collapsed ? null : this.props.label;
|
|
|
|
|
2015-11-03 21:25:08 -05:00
|
|
|
//console.log("render: " + JSON.stringify(this.state.sortedList));
|
|
|
|
|
2015-11-05 06:21:45 -05:00
|
|
|
var target;
|
|
|
|
if (this.state.sortedList.length == 0 && this.props.editable) {
|
|
|
|
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
|
|
|
|
}
|
|
|
|
|
2017-04-17 15:53:46 -04:00
|
|
|
var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
|
|
|
|
|
|
|
|
var isIncomingCallRoom;
|
|
|
|
if (this.props.incomingCall) {
|
|
|
|
// Check if the incoming call is for this section
|
|
|
|
isIncomingCallRoom = this.props.list.find(room=>{
|
|
|
|
return this.props.incomingCall.roomId === room.roomId;
|
|
|
|
}) ? true : false;
|
|
|
|
}
|
|
|
|
|
2015-11-03 19:19:37 -05:00
|
|
|
if (this.state.sortedList.length > 0 || this.props.editable) {
|
2015-11-08 09:08:17 -05:00
|
|
|
var subList;
|
2015-12-18 12:13:57 -05:00
|
|
|
var classes = "mx_RoomSubList";
|
2015-11-08 09:08:17 -05:00
|
|
|
|
|
|
|
if (!this.state.hidden) {
|
2016-01-22 10:31:42 -05:00
|
|
|
subList = <TruncatedList className={ classes } truncateAt={this.state.truncateAt}
|
|
|
|
createOverflowElement={this._createOverflowTile} >
|
2015-11-08 09:08:17 -05:00
|
|
|
{ target }
|
|
|
|
{ this.makeRoomTiles() }
|
2016-01-22 10:31:42 -05:00
|
|
|
</TruncatedList>;
|
2015-11-08 09:08:17 -05:00
|
|
|
}
|
|
|
|
else {
|
2016-01-22 10:31:42 -05:00
|
|
|
subList = <TruncatedList className={ classes }>
|
2016-03-15 14:38:24 -04:00
|
|
|
</TruncatedList>;
|
2015-11-08 09:08:17 -05:00
|
|
|
}
|
|
|
|
|
2015-11-03 21:25:08 -05:00
|
|
|
return connectDropTarget(
|
2015-11-03 19:19:37 -05:00
|
|
|
<div>
|
2017-04-17 15:53:46 -04:00
|
|
|
<RoomSubListHeader
|
2017-04-17 21:43:06 -04:00
|
|
|
ref='header'
|
2017-04-17 15:53:46 -04:00
|
|
|
label={ this.props.label }
|
|
|
|
tagName={ this.props.tagName }
|
|
|
|
roomCount={ roomCount }
|
|
|
|
collapsed={ this.props.collapsed }
|
|
|
|
hidden={ this.state.hidden }
|
|
|
|
isIncomingCallRoom={ isIncomingCallRoom }
|
2017-04-17 21:43:06 -04:00
|
|
|
roomNotificationCount={ this.roomNotificationCount() }
|
|
|
|
onClick={ this.onClick }
|
2017-04-17 15:53:46 -04:00
|
|
|
onHeaderClick={ this.props.onHeaderClick }
|
|
|
|
/>
|
2015-11-06 15:25:20 -05:00
|
|
|
{ subList }
|
2015-11-03 19:19:37 -05:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
2015-12-18 10:17:18 -05:00
|
|
|
var Loader = sdk.getComponent("elements.Spinner");
|
2015-11-03 19:19:37 -05:00
|
|
|
return (
|
|
|
|
<div className="mx_RoomSubList">
|
2017-04-17 15:53:46 -04:00
|
|
|
{ this.props.alwaysShowHeader ?
|
|
|
|
<RoomSubListHeader
|
2017-04-17 21:43:06 -04:00
|
|
|
ref='header'
|
2017-04-17 15:53:46 -04:00
|
|
|
label={ this.props.label }
|
|
|
|
tagName={ this.props.tagName }
|
|
|
|
roomCount={ roomCount }
|
|
|
|
collapsed={ this.props.collapsed }
|
|
|
|
hidden={ this.state.hidden }
|
|
|
|
isIncomingCallRoom={ isIncomingCallRoom }
|
2017-04-17 21:43:06 -04:00
|
|
|
roomNotificationCount={ this.roomNotificationCount() }
|
|
|
|
onClick={ this.onClick }
|
2017-04-17 15:53:46 -04:00
|
|
|
onHeaderClick={ this.props.onHeaderClick }
|
|
|
|
/>
|
|
|
|
: undefined }
|
2015-12-18 10:17:18 -05:00
|
|
|
{ (this.props.showSpinner && !this.state.hidden) ? <Loader /> : undefined }
|
2015-11-03 19:19:37 -05:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-11-03 21:25:08 -05:00
|
|
|
// Export the wrapped version, inlining the 'collect' functions
|
|
|
|
// to more closely resemble the ES7
|
2016-03-15 14:38:24 -04:00
|
|
|
module.exports =
|
2015-11-03 21:25:08 -05:00
|
|
|
DropTarget('RoomTile', roomListTarget, function(connect) {
|
|
|
|
return {
|
|
|
|
connectDropTarget: connect.dropTarget(),
|
|
|
|
}
|
|
|
|
})(RoomSubList);
|