diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 7378e982ef..f32f105889 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.stop(); if (DMRoomMap.shared()) DMRoomMap.shared().stop(); const cli = MatrixClientPeg.get(); if (cli) { diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 534a490a32..bd88327b7f 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -160,12 +160,7 @@ export default class AppTile extends React.Component { // if it's not remaining on screen, get rid of the PersistedElement container if (!ActiveWidgetStore.getWidgetPersistence(this.props.id)) { - // FIXME: ActiveWidgetStore should probably worry about this? - const PersistedElement = sdk.getComponent("elements.PersistedElement"); - PersistedElement.destroyElement(this._persistKey); - ActiveWidgetStore.delWidgetMessaging(this.props.id); - ActiveWidgetStore.delWidgetCapabilities(this.props.id); - ActiveWidgetStore.delRoomId(this.props.id); + ActiveWidgetStore.destroyPersistentWidget(); } } @@ -439,6 +434,9 @@ export default class AppTile extends React.Component { console.warn('Revoking permission to load widget - ', this.state.widgetUrl); localStorage.removeItem(this.state.widgetPermissionId); this.setState({hasPermissionToLoad: false}); + + // Force the widget to be non-persistent + ActiveWidgetStore.destroyPersistentWidget(); } formatAppTileName() { 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..cc27febaf9 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,46 @@ 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() { + if (MatrixClientPeg.get()) { + 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) { + this.destroyPersistentWidget(); + } + } + + destroyPersistentWidget() { + const toDeleteId = this._persistentWidgetId; + + const PersistedElement = sdk.getComponent("elements.PersistedElement"); + PersistedElement.destroyElement('widget_' + toDeleteId); + this.setWidgetPersistence(toDeleteId, false); + this.delWidgetMessaging(toDeleteId); + this.delWidgetCapabilities(toDeleteId); + this.delRoomId(toDeleteId); } setWidgetPersistence(widgetId, val) { @@ -43,6 +89,7 @@ class ActiveWidgetStore { } else if (this._persistentWidgetId !== widgetId && val) { this._persistentWidgetId = widgetId; } + this.emit('update'); } getWidgetPersistence(widgetId) { @@ -55,6 +102,7 @@ class ActiveWidgetStore { setWidgetCapabilities(widgetId, caps) { this._capsByWidgetId[widgetId] = caps; + this.emit('update'); } widgetHasCapability(widgetId, cap) { @@ -63,10 +111,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 +131,7 @@ class ActiveWidgetStore { console.error('Failed to stop listening for widgetMessaging events', e.message); } delete this._widgetMessagingByWidgetId[widgetId]; + this.emit('update'); } } @@ -90,10 +141,12 @@ class ActiveWidgetStore { setRoomId(widgetId, roomId) { this._roomIdByWidgetId[widgetId] = roomId; + this.emit('update'); } delRoomId(widgetId) { delete this._roomIdByWidgetId[widgetId]; + this.emit('update'); } } diff --git a/src/stores/LifecycleStore.js b/src/stores/LifecycleStore.js index 0d76f06e72..2ce3be5a33 100644 --- a/src/stores/LifecycleStore.js +++ b/src/stores/LifecycleStore.js @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd +Copyright 2018 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.