diff --git a/package.json b/package.json index 21add8ccb7..444d1c5369 100644 --- a/package.json +++ b/package.json @@ -65,12 +65,11 @@ "lodash": "^4.13.1", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "optimist": "^0.6.1", - "prop-types": "^15.5.8", "q": "^1.4.1", "react": "^15.4.0", "react-addons-css-transition-group": "15.3.2", "react-dom": "^15.4.0", - "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#39d858c", + "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef", "sanitize-html": "^1.11.1", "text-encoding-utf-8": "^1.0.1", "velocity-vector": "vector-im/velocity#059e3b2", diff --git a/src/ConstantTimeDispatcher.js b/src/ConstantTimeDispatcher.js deleted file mode 100644 index 6c2c3266aa..0000000000 --- a/src/ConstantTimeDispatcher.js +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -// singleton which dispatches invocations of a given type & argument -// rather than just a type (as per EventEmitter and Flux's dispatcher etc) -// -// This means you can have a single point which listens for an EventEmitter event -// and then dispatches out to one of thousands of RoomTiles (for instance) rather than -// having each RoomTile register for the EventEmitter event and having to -// iterate over all of them. -class ConstantTimeDispatcher { - constructor() { - // type -> arg -> [ listener(arg, params) ] - this.listeners = {}; - } - - register(type, arg, listener) { - if (!this.listeners[type]) this.listeners[type] = {}; - if (!this.listeners[type][arg]) this.listeners[type][arg] = []; - this.listeners[type][arg].push(listener); - } - - unregister(type, arg, listener) { - if (this.listeners[type] && this.listeners[type][arg]) { - var i = this.listeners[type][arg].indexOf(listener); - if (i > -1) { - this.listeners[type][arg].splice(i, 1); - } - } - else { - console.warn("Unregistering unrecognised listener (type=" + type + ", arg=" + arg + ")"); - } - } - - dispatch(type, arg, params) { - if (!this.listeners[type] || !this.listeners[type][arg]) { - //console.warn("No registered listeners for dispatch (type=" + type + ", arg=" + arg + ")"); - return; - } - this.listeners[type][arg].forEach(listener=>{ - listener.call(arg, params); - }); - } -} - -if (!global.constantTimeDispatcher) { - global.constantTimeDispatcher = new ConstantTimeDispatcher(); -} -module.exports = global.constantTimeDispatcher; diff --git a/src/KeyCode.js b/src/KeyCode.js index f164dbc15c..c9cac01239 100644 --- a/src/KeyCode.js +++ b/src/KeyCode.js @@ -32,5 +32,4 @@ module.exports = { DELETE: 46, KEY_D: 68, KEY_E: 69, - KEY_K: 75, }; diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index c4eeb03d5f..25ca025a23 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -107,18 +107,6 @@ export default React.createClass({ var handled = false; switch (ev.keyCode) { - case KeyCode.ESCAPE: - - // Implemented this way so possible handling for other pages is neater - switch (this.props.page_type) { - case PageTypes.UserSettings: - this.props.onUserSettingsClose(); - handled = true; - break; - } - - break; - case KeyCode.UP: case KeyCode.DOWN: if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) { diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index de43bd1c19..8794713501 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -590,7 +590,6 @@ var TimelinePanel = React.createClass({ this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0); dis.dispatch({ action: 'on_room_read', - room: this.props.timelineSet.room, }); } } diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index 02460148b3..0b2ca5225d 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -47,35 +47,18 @@ export default React.createClass({ children: React.PropTypes.node, }, - componentWillMount: function() { - this.priorActiveElement = document.activeElement; - }, - - componentWillUnmount: function() { - if (this.priorActiveElement !== null) { - this.priorActiveElement.focus(); - } - }, - - // Don't let key{down,press} events escape the modal. Consume them all. - _eatKeyEvent: function(e) { - e.stopPropagation(); - }, - - // Must be when the key is released (and not pressed) otherwise componentWillUnmount - // will focus another element which will receive future key events - _onKeyUp: function(e) { + _onKeyDown: function(e) { if (e.keyCode === KeyCode.ESCAPE) { + e.stopPropagation(); e.preventDefault(); this.props.onFinished(); } else if (e.keyCode === KeyCode.ENTER) { if (this.props.onEnterPressed) { + e.stopPropagation(); e.preventDefault(); this.props.onEnterPressed(e); } } - // Consume all keyup events while Modal is open - e.stopPropagation(); }, _onCancelClick: function(e) { @@ -84,13 +67,9 @@ export default React.createClass({ render: function() { const TintableSvg = sdk.getComponent("elements.TintableSvg"); - + return ( -
+
diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js index 8e20b0d2bc..6012541b94 100644 --- a/src/components/views/dialogs/QuestionDialog.js +++ b/src/components/views/dialogs/QuestionDialog.js @@ -47,12 +47,6 @@ export default React.createClass({ this.props.onFinished(false); }, - componentDidMount: function() { - if (this.props.focus) { - this.refs.button.focus(); - } - }, - render: function() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const cancelButton = this.props.hasCancelButton ? ( @@ -69,7 +63,7 @@ export default React.createClass({ {this.props.description}
- {this.props.extraButtons} diff --git a/src/components/views/elements/ActionButton.js b/src/components/views/elements/ActionButton.js deleted file mode 100644 index 267388daf6..0000000000 --- a/src/components/views/elements/ActionButton.js +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import AccessibleButton from './AccessibleButton'; -import dis from '../../../dispatcher'; -import sdk from '../../../index'; - -export default React.createClass({ - displayName: 'RoleButton', - - propTypes: { - size: PropTypes.string, - tooltip: PropTypes.bool, - action: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - iconPath: PropTypes.string.isRequired, - }, - - getDefaultProps: function() { - return { - size: "25", - tooltip: false, - }; - }, - - getInitialState: function() { - return { - showTooltip: false, - }; - }, - - _onClick: function(ev) { - ev.stopPropagation(); - dis.dispatch({action: this.props.action}); - }, - - _onMouseEnter: function() { - if (this.props.tooltip) this.setState({showTooltip: true}); - }, - - _onMouseLeave: function() { - this.setState({showTooltip: false}); - }, - - render: function() { - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - - let tooltip; - if (this.state.showTooltip) { - const RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); - tooltip = ; - } - - return ( - - - {tooltip} - - ); - } -}); diff --git a/src/components/views/elements/CreateRoomButton.js b/src/components/views/elements/CreateRoomButton.js deleted file mode 100644 index 73c984a860..0000000000 --- a/src/components/views/elements/CreateRoomButton.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import PropTypes from 'prop-types'; - -const CreateRoomButton = function(props) { - const ActionButton = sdk.getComponent('elements.ActionButton'); - return ( - - ); -}; - -CreateRoomButton.propTypes = { - size: PropTypes.string, - tooltip: PropTypes.bool, -}; - -export default CreateRoomButton; diff --git a/src/components/views/elements/HomeButton.js b/src/components/views/elements/HomeButton.js deleted file mode 100644 index 5c446f24c9..0000000000 --- a/src/components/views/elements/HomeButton.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import PropTypes from 'prop-types'; - -const HomeButton = function(props) { - const ActionButton = sdk.getComponent('elements.ActionButton'); - return ( - - ); -}; - -HomeButton.propTypes = { - size: PropTypes.string, - tooltip: PropTypes.bool, -}; - -export default HomeButton; diff --git a/src/components/views/elements/RoomDirectoryButton.js b/src/components/views/elements/RoomDirectoryButton.js deleted file mode 100644 index 5e68776a15..0000000000 --- a/src/components/views/elements/RoomDirectoryButton.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import PropTypes from 'prop-types'; - -const RoomDirectoryButton = function(props) { - const ActionButton = sdk.getComponent('elements.ActionButton'); - return ( - - ); -}; - -RoomDirectoryButton.propTypes = { - size: PropTypes.string, - tooltip: PropTypes.bool, -}; - -export default RoomDirectoryButton; diff --git a/src/components/views/elements/SettingsButton.js b/src/components/views/elements/SettingsButton.js deleted file mode 100644 index c6438da277..0000000000 --- a/src/components/views/elements/SettingsButton.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import PropTypes from 'prop-types'; - -const SettingsButton = function(props) { - const ActionButton = sdk.getComponent('elements.ActionButton'); - return ( - - ); -}; - -SettingsButton.propTypes = { - size: PropTypes.string, - tooltip: PropTypes.bool, -}; - -export default SettingsButton; diff --git a/src/components/views/elements/StartChatButton.js b/src/components/views/elements/StartChatButton.js deleted file mode 100644 index 747f75d1b3..0000000000 --- a/src/components/views/elements/StartChatButton.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2017 Vector Creations 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. -*/ - -import React from 'react'; -import sdk from '../../../index'; -import PropTypes from 'prop-types'; - -const StartChatButton = function(props) { - const ActionButton = sdk.getComponent('elements.ActionButton'); - return ( - - ); -}; - -StartChatButton.propTypes = { - size: PropTypes.string, - tooltip: PropTypes.bool, -}; - -export default StartChatButton; diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index a595a91ba9..611dd10780 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -1,6 +1,5 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,23 +21,15 @@ var GeminiScrollbar = require('react-gemini-scrollbar'); var MatrixClientPeg = require("../../../MatrixClientPeg"); var CallHandler = require('../../../CallHandler'); var RoomListSorter = require("../../../RoomListSorter"); +var Unread = require('../../../Unread'); var dis = require("../../../dispatcher"); var sdk = require('../../../index'); var rate_limited_func = require('../../../ratelimitedfunc'); var Rooms = require('../../../Rooms'); import DMRoomMap from '../../../utils/DMRoomMap'; var Receipt = require('../../../utils/Receipt'); -var constantTimeDispatcher = require('../../../ConstantTimeDispatcher'); -import AccessibleButton from '../elements/AccessibleButton'; -const HIDE_CONFERENCE_CHANS = true; - -const VERBS = { - 'm.favourite': 'favourite', - 'im.vector.fake.direct': 'tag direct chat', - 'im.vector.fake.recent': 'restore', - 'm.lowpriority': 'demote', -}; +var HIDE_CONFERENCE_CHANS = true; module.exports = React.createClass({ displayName: 'RoomList', @@ -46,23 +37,13 @@ module.exports = React.createClass({ propTypes: { ConferenceHandler: React.PropTypes.any, collapsed: React.PropTypes.bool.isRequired, - selectedRoom: React.PropTypes.string, + currentRoom: React.PropTypes.string, searchFilter: React.PropTypes.string, }, - shouldComponentUpdate: function(nextProps, nextState) { - if (nextProps.collapsed !== this.props.collapsed) return true; - if (nextProps.searchFilter !== this.props.searchFilter) return true; - if (nextState.lists !== this.state.lists || - nextState.isLoadingLeftRooms !== this.state.isLoadingLeftRooms || - nextState.incomingCall !== this.state.incomingCall) return true; - return false; - }, - getInitialState: function() { return { isLoadingLeftRooms: false, - totalRoomCount: null, lists: {}, incomingCall: null, }; @@ -76,21 +57,12 @@ module.exports = React.createClass({ cli.on("Room.name", this.onRoomName); cli.on("Room.tags", this.onRoomTags); cli.on("Room.receipt", this.onRoomReceipt); - cli.on("RoomState.members", this.onRoomStateMember); + cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomMember.name", this.onRoomMemberName); cli.on("accountData", this.onAccountData); - // lookup for which lists a given roomId is currently in. - this.listsForRoomId = {}; - - this.refreshRoomList(); - - // order of the sublists - //this.listOrder = []; - - // loop count to stop a stack overflow if the user keeps waggling the - // mouse for >30s in a row, or if running under mocha - this._delayedRefreshRoomListLoopCount = 0 + var s = this.getRoomLists(); + this.setState(s); }, componentDidMount: function() { @@ -99,22 +71,7 @@ module.exports = React.createClass({ this._updateStickyHeaders(true); }, - componentWillReceiveProps: function(nextProps) { - // short-circuit react when the room changes - // to avoid rerendering all the sublists everywhere - if (nextProps.selectedRoom !== this.props.selectedRoom) { - if (this.props.selectedRoom) { - constantTimeDispatcher.dispatch( - "RoomTile.select", this.props.selectedRoom, {} - ); - } - constantTimeDispatcher.dispatch( - "RoomTile.select", nextProps.selectedRoom, { selected: true } - ); - } - }, - - componentDidUpdate: function(prevProps, prevState) { + componentDidUpdate: function() { // Reinitialise the stickyHeaders when the component is updated this._updateStickyHeaders(true); this._repositionIncomingCallBox(undefined, false); @@ -140,24 +97,10 @@ module.exports = React.createClass({ } break; case 'on_room_read': - // poke the right RoomTile to refresh, using the constantTimeDispatcher - // to avoid each and every RoomTile registering to the 'on_room_read' event - // XXX: if we like the constantTimeDispatcher we might want to dispatch - // directly from TimelinePanel rather than needlessly bouncing via here. - constantTimeDispatcher.dispatch( - "RoomTile.refresh", payload.room.roomId, {} - ); - - // also have to poke the right list(s) - var lists = this.listsForRoomId[payload.room.roomId]; - if (lists) { - lists.forEach(list=>{ - constantTimeDispatcher.dispatch( - "RoomSubList.refreshHeader", list, { room: payload.room } - ); - }); - } - + // Force an update because the notif count state is too deep to cause + // an update. This forces the local echo of reading notifs to be + // reflected by the RoomTiles. + this.forceUpdate(); break; } }, @@ -171,7 +114,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags); MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt); - MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); + MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName); MatrixClientPeg.get().removeListener("accountData", this.onAccountData); } @@ -180,14 +123,10 @@ module.exports = React.createClass({ }, onRoom: function(room) { - // XXX: this happens rarely; ideally we should only update the correct - // sublists when it does (e.g. via a constantTimeDispatch to the right sublist) this._delayedRefreshRoomList(); }, onDeleteRoom: function(roomId) { - // XXX: this happens rarely; ideally we should only update the correct - // sublists when it does (e.g. via a constantTimeDispatch to the right sublist) this._delayedRefreshRoomList(); }, @@ -210,10 +149,6 @@ module.exports = React.createClass({ } }, - _onMouseOver: function(ev) { - this._lastMouseOverTs = Date.now(); - }, - onSubListHeaderClick: function(isHidden, scrollToPosition) { // The scroll area has expanded or contracted, so re-calculate sticky headers positions this._updateStickyHeaders(true, scrollToPosition); @@ -223,98 +158,41 @@ module.exports = React.createClass({ if (toStartOfTimeline) return; if (!room) return; if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return; - - // rather than regenerate our full roomlists, which is very heavy, we poke the - // correct sublists to just re-sort themselves. This isn't enormously reacty, - // but is much faster than the default react reconciler, or having to do voodoo - // with shouldComponentUpdate and a pleaseRefresh property or similar. - var lists = this.listsForRoomId[room.roomId]; - if (lists) { - lists.forEach(list=>{ - constantTimeDispatcher.dispatch("RoomSubList.sort", list, { room: room }); - }); - } - - // we have to explicitly hit the roomtile which just changed - constantTimeDispatcher.dispatch( - "RoomTile.refresh", room.roomId, {} - ); + this._delayedRefreshRoomList(); }, onRoomReceipt: function(receiptEvent, room) { // because if we read a notification, it will affect notification count // only bother updating if there's a receipt from us if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) { - var lists = this.listsForRoomId[room.roomId]; - if (lists) { - lists.forEach(list=>{ - constantTimeDispatcher.dispatch( - "RoomSubList.refreshHeader", list, { room: room } - ); - }); - } - - // we have to explicitly hit the roomtile which just changed - constantTimeDispatcher.dispatch( - "RoomTile.refresh", room.roomId, {} - ); + this._delayedRefreshRoomList(); } }, onRoomName: function(room) { - constantTimeDispatcher.dispatch( - "RoomTile.refresh", room.roomId, {} - ); - }, - - onRoomTags: function(event, room) { - // XXX: this happens rarely; ideally we should only update the correct - // sublists when it does (e.g. via a constantTimeDispatch to the right sublist) this._delayedRefreshRoomList(); }, - onRoomStateMember: function(ev, state, member) { - if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId && - ev.getPrevContent() && ev.getPrevContent().membership === "invite") - { - this._delayedRefreshRoomList(); - } - else { - constantTimeDispatcher.dispatch( - "RoomTile.refresh", member.roomId, {} - ); - } + onRoomTags: function(event, room) { + this._delayedRefreshRoomList(); + }, + + onRoomStateEvents: function(ev, state) { + this._delayedRefreshRoomList(); }, onRoomMemberName: function(ev, member) { - constantTimeDispatcher.dispatch( - "RoomTile.refresh", member.roomId, {} - ); + this._delayedRefreshRoomList(); }, onAccountData: function(ev) { if (ev.getType() == 'm.direct') { - // XXX: this happens rarely; ideally we should only update the correct - // sublists when it does (e.g. via a constantTimeDispatch to the right sublist) - this._delayedRefreshRoomList(); - } - else if (ev.getType() == 'm.push_rules') { this._delayedRefreshRoomList(); } }, _delayedRefreshRoomList: new rate_limited_func(function() { - // if the mouse has been moving over the RoomList in the last 500ms - // then delay the refresh further to avoid bouncing around under the - // cursor - if (Date.now() - this._lastMouseOverTs > 500 || this._delayedRefreshRoomListLoopCount > 60) { - this.refreshRoomList(); - this._delayedRefreshRoomListLoopCount = 0; - } - else { - this._delayedRefreshRoomListLoopCount++; - this._delayedRefreshRoomList(); - } + this.refreshRoomList(); }, 500), refreshRoomList: function() { @@ -322,36 +200,27 @@ module.exports = React.createClass({ // (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs)) // ); - // TODO: ideally we'd calculate this once at start, and then maintain - // any changes to it incrementally, updating the appropriate sublists - // as needed. - // Alternatively we'd do something magical with Immutable.js or similar. - const lists = this.getRoomLists(); - let totalRooms = 0; - for (const l of Object.values(lists)) { - totalRooms += l.length; - } - this.setState({ - lists: this.getRoomLists(), - totalRoomCount: totalRooms, - }); - + // 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._lastRefreshRoomListTs = Date.now(); }, getRoomLists: function() { var self = this; - const lists = {}; + var s = { lists: {} }; - lists["im.vector.fake.invite"] = []; - lists["m.favourite"] = []; - lists["im.vector.fake.recent"] = []; - lists["im.vector.fake.direct"] = []; - lists["m.lowpriority"] = []; - lists["im.vector.fake.archived"] = []; - - this.listsForRoomId = {}; - var otherTagNames = {}; + s.lists["im.vector.fake.invite"] = []; + s.lists["m.favourite"] = []; + s.lists["im.vector.fake.recent"] = []; + s.lists["im.vector.fake.direct"] = []; + s.lists["m.lowpriority"] = []; + s.lists["im.vector.fake.archived"] = []; const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); @@ -364,13 +233,8 @@ module.exports = React.createClass({ // ", target = " + me.events.member.getStateKey() + // ", prevMembership = " + me.events.member.getPrevContent().membership); - if (!self.listsForRoomId[room.roomId]) { - self.listsForRoomId[room.roomId] = []; - } - if (me.membership == "invite") { - self.listsForRoomId[room.roomId].push("im.vector.fake.invite"); - lists["im.vector.fake.invite"].push(room); + s.lists["im.vector.fake.invite"].push(room); } else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) { // skip past this room & don't put it in any lists @@ -380,52 +244,70 @@ module.exports = React.createClass({ { // Used to split rooms via tags var tagNames = Object.keys(room.tags); + if (tagNames.length) { for (var i = 0; i < tagNames.length; i++) { var tagName = tagNames[i]; - lists[tagName] = lists[tagName] || []; - lists[tagName].push(room); - self.listsForRoomId[room.roomId].push(tagName); - otherTagNames[tagName] = 1; + s.lists[tagName] = s.lists[tagName] || []; + s.lists[tagNames[i]].push(room); } } else if (dmRoomMap.getUserIdForRoomId(room.roomId)) { // "Direct Message" rooms (that we're still in and that aren't otherwise tagged) - self.listsForRoomId[room.roomId].push("im.vector.fake.direct"); - lists["im.vector.fake.direct"].push(room); + s.lists["im.vector.fake.direct"].push(room); } else { - self.listsForRoomId[room.roomId].push("im.vector.fake.recent"); - lists["im.vector.fake.recent"].push(room); + s.lists["im.vector.fake.recent"].push(room); } } else if (me.membership === "leave") { - self.listsForRoomId[room.roomId].push("im.vector.fake.archived"); - lists["im.vector.fake.archived"].push(room); + s.lists["im.vector.fake.archived"].push(room); } else { console.error("unrecognised membership: " + me.membership + " - this should never happen"); } }); + if (s.lists["im.vector.fake.direct"].length == 0 && + MatrixClientPeg.get().getAccountData('m.direct') === undefined && + !MatrixClientPeg.get().isGuest()) + { + // scan through the 'recents' list for any rooms which look like DM rooms + // and make them DM rooms + const oldRecents = s.lists["im.vector.fake.recent"]; + s.lists["im.vector.fake.recent"] = []; + + for (const room of oldRecents) { + const me = room.getMember(MatrixClientPeg.get().credentials.userId); + + if (me && Rooms.looksLikeDirectMessageRoom(room, me)) { + s.lists["im.vector.fake.direct"].push(room); + } else { + s.lists["im.vector.fake.recent"].push(room); + } + } + + // save these new guessed DM rooms into the account data + const newMDirectEvent = {}; + for (const room of s.lists["im.vector.fake.direct"]) { + const me = room.getMember(MatrixClientPeg.get().credentials.userId); + const otherPerson = Rooms.getOnlyOtherMember(room, me); + if (!otherPerson) continue; + + const roomList = newMDirectEvent[otherPerson.userId] || []; + roomList.push(room.roomId); + newMDirectEvent[otherPerson.userId] = roomList; + } + + // if this fails, fine, we'll just do the same thing next time we get the room lists + MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done(); + } + + //console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]); + // we actually apply the sorting to this when receiving the prop in RoomSubLists. - // we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down -/* - this.listOrder = [ - "im.vector.fake.invite", - "m.favourite", - "im.vector.fake.recent", - "im.vector.fake.direct", - Object.keys(otherTagNames).filter(tagName=>{ - return (!tagName.match(/^m\.(favourite|lowpriority)$/)); - }).sort(), - "m.lowpriority", - "im.vector.fake.archived" - ]; -*/ - - return lists; + return s; }, _getScrollNode: function() { @@ -455,7 +337,6 @@ module.exports = React.createClass({ var incomingCallBox = document.getElementById("incomingCallBox"); if (incomingCallBox && incomingCallBox.parentElement) { var scrollArea = this._getScrollNode(); - if (!scrollArea) return; // Use the offset of the top of the scroll area from the window // as this is used to calculate the CSS fixed top position for the stickies var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset; @@ -479,7 +360,6 @@ module.exports = React.createClass({ // properly through React _initAndPositionStickyHeaders: function(initialise, scrollToPosition) { var scrollArea = this._getScrollNode(); - if (!scrollArea) return; // Use the offset of the top of the scroll area from the window // as this is used to calculate the CSS fixed top position for the stickies var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset; @@ -577,74 +457,21 @@ module.exports = React.createClass({ this.refs.gemscroll.forceUpdate(); }, - _getEmptyContent: function(section) { - const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget'); - - if (this.props.collapsed) { - return ; - } - - const StartChatButton = sdk.getComponent('elements.StartChatButton'); - const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton'); - const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton'); - if (this.state.totalRoomCount === 0) { - const TintableSvg = sdk.getComponent('elements.TintableSvg'); - switch (section) { - case 'im.vector.fake.direct': - return
- Press - - to start a chat with someone -
; - case 'im.vector.fake.recent': - return
- You're not in any rooms yet! Press - - to make a room or - - to browse the directory -
; - } - } - - const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section); - - return ; - }, - - _getHeaderItems: function(section) { - const StartChatButton = sdk.getComponent('elements.StartChatButton'); - const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton'); - const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton'); - switch (section) { - case 'im.vector.fake.direct': - return - - ; - case 'im.vector.fake.recent': - return - - - ; - } - }, - render: function() { var RoomSubList = sdk.getComponent('structures.RoomSubList'); var self = this; return ( -
+ autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll"> +
@@ -652,12 +479,12 @@ module.exports = React.createClass({ @@ -665,13 +492,12 @@ module.exports = React.createClass({ - { Object.keys(self.state.lists).sort().map(function(tagName) { + { Object.keys(self.state.lists).map(function(tagName) { if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) { return ; @@ -713,23 +537,22 @@ module.exports = React.createClass({ 0 || this.props.isInvite, - }); - }, - - onSelect: function(params) { - this.setState({ - selected: params.selected, - }); + var cli = MatrixClientPeg.get(); + if (cli) { + MatrixClientPeg.get().removeListener("accountData", this.onAccountData); + } }, onClick: function(ev) { @@ -179,13 +169,13 @@ module.exports = React.createClass({ // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); - const mentionBadges = this.state.highlight && this._shouldShowMentionBadge(); + const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); const badges = notifBadges || mentionBadges; var classes = classNames({ 'mx_RoomTile': true, - 'mx_RoomTile_selected': this.state.selected, - 'mx_RoomTile_unread': this.state.unread, + 'mx_RoomTile_selected': this.props.selected, + 'mx_RoomTile_unread': this.props.unread, 'mx_RoomTile_unreadNotify': notifBadges, 'mx_RoomTile_highlight': mentionBadges, 'mx_RoomTile_invited': (me && me.membership == 'invite'), @@ -231,7 +221,7 @@ module.exports = React.createClass({ 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, }); - if (this.state.selected) { + if (this.props.selected) { let nameSelected = {name}; label =
{ nameSelected }
; @@ -265,8 +255,7 @@ module.exports = React.createClass({ let ret = (
{ /* Only native elements can be wrapped in a DnD object. */} - +