diff --git a/src/ActiveRoomObserver.js b/src/ActiveRoomObserver.js new file mode 100644 index 00000000000..d6fbb460b5d --- /dev/null +++ b/src/ActiveRoomObserver.js @@ -0,0 +1,77 @@ +/* +Copyright 2017 New Vector 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 RoomViewStore from './stores/RoomViewStore'; + +/** + * Consumes changes from the RoomViewStore and notifies specific things + * about when the active room changes. Unlike listening for RoomViewStore + * changes, you can subscribe to only changes relevant to a particular + * room. + * + * TODO: If we introduce an observer for something else, factor out + * the adding / removing of listeners & emitting into a common class. + */ +class ActiveRoomObserver { + constructor() { + this._listeners = {}; + + this._activeRoomId = RoomViewStore.getRoomId(); + // TODO: We could self-destruct when the last listener goes away, or at least + // stop listening. + this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this)); + } + + addListener(roomId, listener) { + if (!this._listeners[roomId]) this._listeners[roomId] = []; + this._listeners[roomId].push(listener); + } + + removeListener(roomId, listener) { + if (this._listeners[roomId]) { + const i = this._listeners[roomId].indexOf(listener); + if (i > -1) { + this._listeners[roomId].splice(i, 1); + } + } else { + console.warn("Unregistering unrecognised listener (roomId=" + roomId + ")"); + } + } + + _emit(roomId) { + if (!this._listeners[roomId]) return; + + for (const l of this._listeners[roomId]) { + l.call(); + } + } + + _onRoomViewStoreUpdate() { + // emit for the old room ID + if (this._activeRoomId) this._emit(this._activeRoomId); + + // update our cache + this._activeRoomId = RoomViewStore.getRoomId(); + + // and emit for the new one + if (this._activeRoomId) this._emit(this._activeRoomId); + } +} + +if (global.mx_ActiveRoomObserver === undefined) { + global.mx_ActiveRoomObserver = new ActiveRoomObserver(); +} +export default global.mx_ActiveRoomObserver; diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 27001ac9544..b7b2e5ecea3 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -63,7 +63,6 @@ module.exports = React.createClass({ propTypes: { ConferenceHandler: React.PropTypes.any, collapsed: React.PropTypes.bool.isRequired, - currentRoom: React.PropTypes.string, searchFilter: React.PropTypes.string, }, diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 97568a52a11..a6065d8e926 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,6 +28,8 @@ var RoomNotifs = require('../../../RoomNotifs'); var FormattingUtils = require('../../../utils/FormattingUtils'); import AccessibleButton from '../elements/AccessibleButton'; var UserSettingsStore = require('../../../UserSettingsStore'); +import ActiveRoomObserver from '../../../ActiveRoomObserver'; +import RoomViewStore from '../../../stores/RoomViewStore'; module.exports = React.createClass({ displayName: 'RoomTile', @@ -39,7 +42,6 @@ module.exports = React.createClass({ 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, @@ -58,6 +60,7 @@ module.exports = React.createClass({ badgeHover : false, menuDisplayed: false, notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), + selected: this.props.room.roomId === RoomViewStore.getRoomId(), }); }, @@ -87,8 +90,15 @@ module.exports = React.createClass({ } }, + _onActiveRoomChange: function() { + this.setState({ + selected: this.props.room.roomId === RoomViewStore.getRoomId(), + }); + }, + componentWillMount: function() { MatrixClientPeg.get().on("accountData", this.onAccountData); + ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange); }, componentWillUnmount: function() { @@ -96,6 +106,7 @@ module.exports = React.createClass({ if (cli) { MatrixClientPeg.get().removeListener("accountData", this.onAccountData); } + ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange); }, onClick: function(ev) { @@ -174,7 +185,7 @@ module.exports = React.createClass({ var classes = classNames({ 'mx_RoomTile': true, - 'mx_RoomTile_selected': this.props.selected, + 'mx_RoomTile_selected': this.state.selected, 'mx_RoomTile_unread': this.props.unread, 'mx_RoomTile_unreadNotify': notifBadges, 'mx_RoomTile_highlight': mentionBadges, @@ -221,7 +232,7 @@ module.exports = React.createClass({ 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, }); - if (this.props.selected) { + if (this.state.selected) { let nameSelected = {name}; label =
{ nameSelected }
; diff --git a/src/components/views/voip/CallPreview.js b/src/components/views/voip/CallPreview.js new file mode 100644 index 00000000000..132e29bd341 --- /dev/null +++ b/src/components/views/voip/CallPreview.js @@ -0,0 +1,97 @@ +/* +Copyright 2017 New Vector 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 RoomViewStore from '../../../stores/RoomViewStore'; +import CallHandler from '../../../CallHandler'; +import dis from '../../../dispatcher'; +import sdk from '../../../index'; + +module.exports = React.createClass({ + displayName: 'CallPreview', + + propTypes: { + // A Conference Handler implementation + // Must have a function signature: + // getConferenceCallForRoom(roomId: string): MatrixCall + ConferenceHandler: React.PropTypes.object, + }, + + getInitialState: function() { + return { + roomId: RoomViewStore.getRoomId(), + activeCall: CallHandler.getAnyActiveCall(), + }; + }, + + componentWillMount: function() { + this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); + this.dispatcherRef = dis.register(this._onAction); + }, + + componentWillUnmount: function() { + if (this._roomStoreToken) { + this._roomStoreToken.remove(); + } + dis.unregister(this.dispatcherRef); + }, + + _onRoomViewStoreUpdate: function(payload) { + if (RoomViewStore.getRoomId() === this.state.roomId) return; + this.setState({ + roomId: RoomViewStore.getRoomId(), + }); + }, + + _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.setState({ + activeCall: CallHandler.getAnyActiveCall(), + }); + break; + } + }, + + _onCallViewClick: function() { + const call = CallHandler.getAnyActiveCall(); + if (call) { + dis.dispatch({ + action: 'view_room', + room_id: call.groupRoomId || call.roomId, + }); + } + }, + + render: function() { + const callForRoom = CallHandler.getCallForRoom(this.state.roomId); + const showCall = (this.state.activeCall && this.state.activeCall.call_state === 'connected' && !callForRoom); + + if (showCall) { + const CallView = sdk.getComponent('voip.CallView'); + return ( + + ); + } + return null; + }, +}); +