diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 7378e982ef..a618cbeadf 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -30,6 +30,7 @@ import DMRoomMap from './utils/DMRoomMap'; import RtsClient from './RtsClient'; import Modal from './Modal'; import sdk from './index'; +import ActiveWidgetStore from './stores/ActiveWidgetStore'; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -436,6 +437,7 @@ async function startMatrixClient() { UserActivity.start(); Presence.start(); DMRoomMap.makeShared().start(); + ActiveWidgetStore.start(); await MatrixClientPeg.start(); @@ -488,6 +490,7 @@ export function stopMatrixClient() { Notifier.stop(); UserActivity.stop(); Presence.stop(); + ActiveWidgetStore.start(); if (DMRoomMap.shared()) DMRoomMap.shared().stop(); const cli = MatrixClientPeg.get(); if (cli) { diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index bbb3b2a9c8..facf5d1179 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -27,17 +27,20 @@ module.exports = React.createClass({ getInitialState: function() { return { roomId: RoomViewStore.getRoomId(), + persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), }; }, componentWillMount: function() { this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); + ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate); }, componentWillUnmount: function() { if (this._roomStoreToken) { this._roomStoreToken.remove(); } + ActiveWidgetStore.removeListener('update', this._onActiveWidgetStoreUpdate); }, _onRoomViewStoreUpdate: function(payload) { @@ -47,9 +50,15 @@ module.exports = React.createClass({ }); }, + _onActiveWidgetStoreUpdate: function() { + this.setState({ + persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), + }); + }, + render: function() { - if (ActiveWidgetStore.getPersistentWidgetId()) { - const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(ActiveWidgetStore.getPersistentWidgetId()); + if (this.state.persistentWidgetId) { + const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId); if (this.state.roomId !== persistentWidgetInRoomId) { const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId); // get the widget data diff --git a/src/stores/ActiveWidgetStore.js b/src/stores/ActiveWidgetStore.js index 01d5f15601..e1b1efaace 100644 --- a/src/stores/ActiveWidgetStore.js +++ b/src/stores/ActiveWidgetStore.js @@ -14,14 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ +import EventEmitter from 'events'; + +import MatrixClientPeg from '../MatrixClientPeg'; +import sdk from '../index'; + /** * Stores information about the widgets active in the app right now: * * What widget is set to remain always-on-screen, if any * Only one widget may be 'always on screen' at any one time. * * Negotiated capabilities for active apps */ -class ActiveWidgetStore { +class ActiveWidgetStore extends EventEmitter { constructor() { + super(); this._persistentWidgetId = null; // A list of negotiated capabilities for each widget, by ID @@ -35,6 +41,38 @@ class ActiveWidgetStore { // What room ID each widget is associated with (if it's a room widget) this._roomIdByWidgetId = {}; + + this.onRoomStateEvents = this.onRoomStateEvents.bind(this); + + this.dispatcherRef = null; + } + + start() { + MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); + } + + stop() { + MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); + this._capsByWidgetId = {}; + this._widgetMessagingByWidgetId = {}; + this._roomIdByWidgetId = {}; + } + + onRoomStateEvents(ev, state) { + // XXX: This listens for state events in order to remove the active widget. + // Everything else relies on views listening for events and calling setters + // on this class which is terrible. This store should just listen for events + // and keep itself up to date. + if (ev.getType() !== 'im.vector.modular.widgets') return; + + if (ev.getStateKey() === this._persistentWidgetId) { + const PersistedElement = sdk.getComponent("elements.PersistedElement"); + PersistedElement.destroyElement('widget_' + ev.getStateKey()); + this.setWidgetPersistence(ev.getStateKey(), false); + this.delWidgetMessaging(ev.getStateKey()); + this.delWidgetCapabilities(ev.getStateKey()); + this.delRoomId(ev.getStateKey()); + } } setWidgetPersistence(widgetId, val) { @@ -43,6 +81,7 @@ class ActiveWidgetStore { } else if (this._persistentWidgetId !== widgetId && val) { this._persistentWidgetId = widgetId; } + this.emit('update'); } getWidgetPersistence(widgetId) { @@ -55,6 +94,7 @@ class ActiveWidgetStore { setWidgetCapabilities(widgetId, caps) { this._capsByWidgetId[widgetId] = caps; + this.emit('update'); } widgetHasCapability(widgetId, cap) { @@ -63,10 +103,12 @@ class ActiveWidgetStore { delWidgetCapabilities(widgetId) { delete this._capsByWidgetId[widgetId]; + this.emit('update'); } setWidgetMessaging(widgetId, wm) { this._widgetMessagingByWidgetId[widgetId] = wm; + this.emit('update'); } getWidgetMessaging(widgetId) { @@ -81,6 +123,7 @@ class ActiveWidgetStore { console.error('Failed to stop listening for widgetMessaging events', e.message); } delete this._widgetMessagingByWidgetId[widgetId]; + this.emit('update'); } } @@ -90,10 +133,12 @@ class ActiveWidgetStore { setRoomId(widgetId, roomId) { this._roomIdByWidgetId[widgetId] = roomId; + this.emit('update'); } delRoomId(widgetId) { delete this._roomIdByWidgetId[widgetId]; + this.emit('update'); } }