diff --git a/src/CallHandler.js b/src/CallHandler.js index 7403483e36..abbd407e7f 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -62,6 +62,7 @@ import dis from './dispatcher'; import { showUnknownDeviceDialogForCalls } from './cryptodevices'; import SettingsStore from "./settings/SettingsStore"; import WidgetUtils from './utils/WidgetUtils'; +import WidgetEchoStore from './stores/WidgetEchoStore'; global.mxCalls = { //room_id: MatrixCall @@ -402,18 +403,30 @@ function _onAction(payload) { } function _startCallApp(roomId, type) { - dis.dispatch({ - action: 'appsDrawer', - show: true, - }); - const room = MatrixClientPeg.get().getRoom(roomId); if (!room) { console.error("Attempted to start conference call widget in unknown room: " + roomId); return; } - const currentJitsiWidgets = WidgetUtils.getRoomWidgets(room).filter((ev) => { + dis.dispatch({ + action: 'appsDrawer', + show: true, + }); + + const currentRoomWidgets = WidgetUtils.getRoomWidgets(room); + + if (WidgetEchoStore.roomHasPendingWidgetsOfType(room, currentRoomWidgets, 'jitsi')) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + + Modal.createTrackedDialog('Already have pending Jitsi Widget', '', ErrorDialog, { + title: _t('Call in Progress'), + description: _t('A call is currently being placed!'), + }); + return; + } + + const currentJitsiWidgets = currentRoomWidgets.filter((ev) => { return ev.getContent().type === 'jitsi'; }); if (currentJitsiWidgets.length > 0) { diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index 6e1d52a88f..d8c315f505 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -183,6 +183,20 @@ function createEventDecryptedAction(matrixClient, event) { return { action: 'MatrixActions.Event.decrypted', event }; } +/** + * Create a MatrixActions.RoomState.vents action that represents + * a MatrixClient `RoomState.events` matrix event, emitted when the + * state events in a room change. + * + * @param {MatrixClient} matrixClient the matrix client. + * @param {MatrixEvent} event matrix event which caused this event to fire. + * @param {RoomState} state room state whose RoomState.events dictionary was updated. + * @returns {EventDecryptedAction} an action of type `MatrixActions.Event.decrypted`. + */ +function createRoomStateEventsAction(matrixClient, event, state) { + return { action: 'MatrixActions.RoomState.events', event, state }; +} + /** * This object is responsible for dispatching actions when certain events are emitted by * the given MatrixClient. @@ -204,6 +218,7 @@ export default { this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); this._addMatrixClientListener(matrixClient, 'RoomMember.membership', createRoomMembershipAction); this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction); + this._addMatrixClientListener(matrixClient, 'RoomState.events', createRoomStateEventsAction); }, /** diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 98d700e0a0..5c97b5c29a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -45,6 +45,7 @@ import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard'; import RoomViewStore from '../../stores/RoomViewStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; +import WidgetEchoStore from '../../stores/WidgetEchoStore'; import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import WidgetUtils from '../../utils/WidgetUtils'; @@ -153,6 +154,8 @@ module.exports = React.createClass({ // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._onRoomViewStoreUpdate(true); + + WidgetEchoStore.on('updateRoomWidgetEcho', this._onWidgetEchoStoreUpdate); }, _onRoomViewStoreUpdate: function(initial) { @@ -243,6 +246,12 @@ module.exports = React.createClass({ } }, + _onWidgetEchoStoreUpdate: function() { + this.setState({ + showApps: this._shouldShowApps(this.state.room), + }); + }, + _setupRoom: function(room, roomId, joining, shouldPeek) { // if this is an unknown room then we're in one of three states: // - This is a room we can peek into (search engine) (we can /peek) @@ -319,7 +328,9 @@ module.exports = React.createClass({ return false; } - return WidgetUtils.getRoomWidgets(room).length > 0; + const widgets = WidgetEchoStore.getEchoedRoomWidgets(room, WidgetUtils.getRoomWidgets(room)); + + return widgets.length > 0 || WidgetEchoStore.roomHasPendingWidgets(room, WidgetUtils.getRoomWidgets(room)); }, componentDidMount: function() { @@ -414,6 +425,8 @@ module.exports = React.createClass({ this._roomStoreToken.remove(); } + WidgetEchoStore.removeListener('updateRoomWidgetEcho', this._onWidgetEchoStoreUpdate); + // cancel any pending calls to the rate_limited_funcs this._updateRoomMembers.cancelPendingCall(); diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 04e47f0f9f..64aa8fadc8 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -29,6 +29,7 @@ import ScalarMessaging from '../../../ScalarMessaging'; import { _t } from '../../../languageHandler'; import WidgetUtils from '../../../utils/WidgetUtils'; import SettingsStore from "../../../settings/SettingsStore"; +import WidgetEchoStore from "../../../stores/WidgetEchoStore"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -57,6 +58,7 @@ module.exports = React.createClass({ componentWillMount: function() { ScalarMessaging.startListening(); MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); + WidgetEchoStore.on('updateRoomWidgetEcho', this._updateApps); }, componentDidMount: function() { @@ -82,6 +84,7 @@ module.exports = React.createClass({ if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); } + WidgetEchoStore.removeListener('updateRoomWidgetEcho', this._updateApps); dis.unregister(this.dispatcherRef); }, @@ -163,7 +166,10 @@ module.exports = React.createClass({ }, _getApps: function() { - return WidgetUtils.getRoomWidgets(this.props.room).map((ev) => { + const widgets = WidgetEchoStore.getEchoedRoomWidgets( + this.props.room, WidgetUtils.getRoomWidgets(this.props.room), + ); + return widgets.map((ev) => { return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender); }); }, @@ -231,7 +237,8 @@ module.exports = React.createClass({ waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={enableScreenshots ? ["m.capability.screenshot"] : []} />); - }); + }, + ); let addWidget; if (this.props.showApps && @@ -250,10 +257,22 @@ module.exports = React.createClass({ ; } + let spinner; + if ( + apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets( + this.props.room, + WidgetUtils.getRoomWidgets(this.props.room), + ) + ) { + const Loader = sdk.getComponent("elements.Spinner"); + spinner = ; + } + return (
{ apps } + { spinner }
{ this._canUserModify() && addWidget }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d02ab8ff29..ae4ca74085 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -40,14 +40,11 @@ "Failed to set up conference call": "Failed to set up conference call", "Conference call failed.": "Conference call failed.", "Call in Progress": "Call in Progress", + "A call is currently being placed!": "A call is currently being placed!", "A call is already in progress!": "A call is already in progress!", "The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload", "The file '%(fileName)s' exceeds this home server's size limit for uploads": "The file '%(fileName)s' exceeds this home server's size limit for uploads", "Upload Failed": "Upload Failed", - "Failure to create room": "Failure to create room", - "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", - "Send anyway": "Send anyway", - "Send": "Send", "Sun": "Sun", "Mon": "Mon", "Tue": "Tue", @@ -87,7 +84,6 @@ "Failed to invite users to community": "Failed to invite users to community", "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", - "Unnamed Room": "Unnamed Room", "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", "Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again", "Unable to enable Notifications": "Unable to enable Notifications", @@ -153,18 +149,59 @@ "Displays action": "Displays action", "Unrecognised command:": "Unrecognised command:", "Reason": "Reason", + " accepted the invitation for %(displayName)s.": " accepted the invitation for %(displayName)s.", + " accepted an invitation.": " accepted an invitation.", + " requested a VoIP conference.": " requested a VoIP conference.", + " invited .": " invited .", + " banned .": " banned .", + " changed their display name to .": " changed their display name to .", + " set their display name to .": " set their display name to .", + " removed their display name ().": " removed their display name ().", + " removed their profile picture.": " removed their profile picture.", + " changed their profile picture.": " changed their profile picture.", + " set a profile picture.": " set a profile picture.", "VoIP conference started.": "VoIP conference started.", + " joined the room.": " joined the room.", "VoIP conference finished.": "VoIP conference finished.", - "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", + " rejected the invitation.": " rejected the invitation.", + " left the room.": " left the room.", + " unbanned .": " unbanned .", + " kicked .": " kicked .", + " withdrew 's invitation.": " withdrew 's invitation.", + " changed the topic to \"%(topic)s\".": " changed the topic to \"%(topic)s\".", + " removed the room name.": " removed the room name.", + " changed the room name to %(roomName)s.": " changed the room name to %(roomName)s.", + " sent an image.": " sent an image.", "Someone": "Someone", "(not supported by this browser)": "(not supported by this browser)", + " answered the call.": " answered the call.", "(could not connect media)": "(could not connect media)", "(no answer)": "(no answer)", "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)", + " ended the call.": " ended the call.", + " placed a %(callType)s call.": " placed a %(callType)s call.", + " sent an invitation to %(targetDisplayName)s to join the room.": " sent an invitation to %(targetDisplayName)s to join the room.", + " made future room history visible to all room members, from the point they are invited.": " made future room history visible to all room members, from the point they are invited.", + " made future room history visible to all room members, from the point they joined.": " made future room history visible to all room members, from the point they joined.", + " made future room history visible to all room members.": " made future room history visible to all room members.", + " made future room history visible to anyone.": " made future room history visible to anyone.", + " made future room history visible to unknown (%(visibility)s).": " made future room history visible to unknown (%(visibility)s).", + " turned on end-to-end encryption (algorithm %(algorithm)s).": " turned on end-to-end encryption (algorithm %(algorithm)s).", + " from %(fromPowerLevel)s to %(toPowerLevel)s": " from %(fromPowerLevel)s to %(toPowerLevel)s", + " changed the power level of %(powerLevelDiffText)s.": " changed the power level of %(powerLevelDiffText)s.", + " changed the pinned messages for the room.": " changed the pinned messages for the room.", + "%(widgetName)s widget modified by ": "%(widgetName)s widget modified by ", + "%(widgetName)s widget added by ": "%(widgetName)s widget added by ", + "%(widgetName)s widget removed by ": "%(widgetName)s widget removed by ", "%(displayName)s is typing": "%(displayName)s is typing", "%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing", "%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing", "%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", + "Failure to create room": "Failure to create room", + "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", + "Send anyway": "Send anyway", + "Send": "Send", + "Unnamed Room": "Unnamed Room", "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", "Not a valid Riot keyfile": "Not a valid Riot keyfile", "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", @@ -277,29 +314,6 @@ "Off": "Off", "On": "On", "Noisy": "Noisy", - "Invalid alias format": "Invalid alias format", - "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias", - "Invalid address format": "Invalid address format", - "'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address", - "not specified": "not specified", - "not set": "not set", - "Remote addresses for this room:": "Remote addresses for this room:", - "Addresses": "Addresses", - "The main address for this room is": "The main address for this room is", - "Local addresses for this room:": "Local addresses for this room:", - "This room has no local addresses": "This room has no local addresses", - "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", - "Invalid community ID": "Invalid community ID", - "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", - "Flair": "Flair", - "Showing flair for these communities:": "Showing flair for these communities:", - "This room is not showing flair for any communities": "This room is not showing flair for any communities", - "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)", - "You have enabled URL previews by default.": "You have enabled URL previews by default.", - "You have disabled URL previews by default.": "You have disabled URL previews by default.", - "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.", - "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", - "URL Previews": "URL Previews", "Cannot add any more widgets": "Cannot add any more widgets", "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", @@ -401,11 +415,11 @@ "numbullet": "numbullet", "Markdown is disabled": "Markdown is disabled", "Markdown is enabled": "Markdown is enabled", + "Unpin Message": "Unpin Message", + "Jump to message": "Jump to message", "No pinned messages.": "No pinned messages.", "Loading...": "Loading...", "Pinned Messages": "Pinned Messages", - "Unpin Message": "Unpin Message", - "Jump to message": "Jump to message", "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", "%(duration)sh": "%(duration)sh", @@ -580,6 +594,9 @@ "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", "Error decrypting video": "Error decrypting video", + "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", + "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", "Copied!": "Copied!", "Failed to copy": "Failed to copy", "Add an Integration": "Add an Integration", @@ -755,7 +772,6 @@ "Room directory": "Room directory", "Start chat": "Start chat", "And %(count)s more...|other": "And %(count)s more...", - "Share Link to User": "Share Link to User", "ex. @bob:example.com": "ex. @bob:example.com", "Add User": "Add User", "Matrix ID": "Matrix ID", @@ -1193,44 +1209,5 @@ "Import": "Import", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", - " accepted the invitation for %(displayName)s.": " accepted the invitation for %(displayName)s.", - " accepted an invitation.": " accepted an invitation.", - " requested a VoIP conference.": " requested a VoIP conference.", - " invited .": " invited .", - " banned .": " banned .", - " changed their display name to .": " changed their display name to .", - " set their display name to .": " set their display name to .", - " removed their display name ().": " removed their display name ().", - " removed their profile picture.": " removed their profile picture.", - " changed their profile picture.": " changed their profile picture.", - " set a profile picture.": " set a profile picture.", - " joined the room.": " joined the room.", - " rejected the invitation.": " rejected the invitation.", - " left the room.": " left the room.", - " unbanned .": " unbanned .", - " kicked .": " kicked .", - " withdrew 's invitation.": " withdrew 's invitation.", - " changed the topic to \"%(topic)s\".": " changed the topic to \"%(topic)s\".", - " changed the room name to %(roomName)s.": " changed the room name to %(roomName)s.", - " changed the avatar for %(roomName)s": " changed the avatar for %(roomName)s", - " changed the room avatar to ": " changed the room avatar to ", - " removed the room name.": " removed the room name.", - " removed the room avatar.": " removed the room avatar.", - " answered the call.": " answered the call.", - " ended the call.": " ended the call.", - " placed a %(callType)s call.": " placed a %(callType)s call.", - " sent an invitation to %(targetDisplayName)s to join the room.": " sent an invitation to %(targetDisplayName)s to join the room.", - " made future room history visible to all room members, from the point they are invited.": " made future room history visible to all room members, from the point they are invited.", - " made future room history visible to all room members, from the point they joined.": " made future room history visible to all room members, from the point they joined.", - " made future room history visible to all room members.": " made future room history visible to all room members.", - " made future room history visible to anyone.": " made future room history visible to anyone.", - " made future room history visible to unknown (%(visibility)s).": " made future room history visible to unknown (%(visibility)s).", - " turned on end-to-end encryption (algorithm %(algorithm)s).": " turned on end-to-end encryption (algorithm %(algorithm)s).", - " from %(fromPowerLevel)s to %(toPowerLevel)s": " from %(fromPowerLevel)s to %(toPowerLevel)s", - " changed the power level of %(powerLevelDiffText)s.": " changed the power level of %(powerLevelDiffText)s.", - " changed the pinned messages for the room.": " changed the pinned messages for the room.", - "%(widgetName)s widget modified by ": "%(widgetName)s widget modified by ", - "%(widgetName)s widget added by ": "%(widgetName)s widget added by ", - "%(widgetName)s widget removed by ": "%(widgetName)s widget removed by " + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" } diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 35cfe69086..fed0d7b4a1 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -1,6 +1,6 @@ /* Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd +Copyright 2017, 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. diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js new file mode 100644 index 0000000000..26d24c0563 --- /dev/null +++ b/src/stores/WidgetEchoStore.js @@ -0,0 +1,112 @@ +/* +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. +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 EventEmitter from 'events'; + +import WidgetUtils from '../utils/WidgetUtils'; +import MatrixClientPeg from '../MatrixClientPeg'; + +/** + * Acts as a place to get & set widget state, storing local echo state and + * proxying through state from the js-sdk. + */ +class WidgetEchoStore extends EventEmitter { + constructor() { + super(); + + this._roomWidgetEcho = { + // roomId: { + // widgetId: [object] + // } + }; + } + + /** + * Gets the widgets for a room, substracting those that are pending deletion. + * Widgets that are pending addition are not included, since widgets are + * represted as MatrixEvents, so to do this we'd have to create fake MatrixEvents, + * and we don't really need the actual widget events anyway since we just want to + * show a spinner / prevent widgets being added twice. + */ + getEchoedRoomWidgets(room, currentRoomWidgets) { + const echoedWidgets = []; + + const roomEchoState = Object.assign({}, this._roomWidgetEcho[room.roomId]); + + for (const w of currentRoomWidgets) { + const widgetId = w.getStateKey(); + if (roomEchoState && roomEchoState[widgetId] && Object.keys(roomEchoState[widgetId]).length === 0) { + // delete locally so don't copy it + // we don't include widgets that have changed for the same rason we don't include new ones, + // so fall into the 'else' case and use the old one + //} else if (roomEchoState && roomEchoState[widgetId]) { + // echoedWidgets.push(roomEchoState[widgetId]); + } else { + echoedWidgets.push(w); + } + delete roomEchoState[widgetId]; + } + + // any remining in roomEchoState are extra that need to be added + // We don't do this for the reasons above + /*for (const widgetId of Object.keys(roomEchoState)) { + echoedWidgets.push(roomEchoState[widgetId]); + }*/ + + return echoedWidgets; + } + + roomHasPendingWidgetsOfType(room, currentRoomWidgets, type) { + const roomEchoState = Object.assign({}, this._roomWidgetEcho[room.roomId]); + if (roomEchoState === undefined) return false; + + for (const w of currentRoomWidgets) { + const widgetId = w.getStateKey(); + delete roomEchoState[widgetId]; + } + + if (type === undefined) { + return Object.keys(roomEchoState).length > 0; + } else { + return Object.values(roomEchoState).some((widget) => { + return widget.type === type; + }); + } + } + + roomHasPendingWidgets(room, currentRoomWidgets) { + return this.roomHasPendingWidgetsOfType(room, currentRoomWidgets); + } + + setRoomWidgetEcho(room, widgetId, state) { + if (this._roomWidgetEcho[room.roomId] === undefined) this._roomWidgetEcho[room.roomId] = {}; + + this._roomWidgetEcho[room.roomId][widgetId] = state; + this.emit('updateRoomWidgetEcho'); + } + + removeRoomWidgetEcho(room, widgetId) { + delete this._roomWidgetEcho[room.roomId][widgetId]; + if (this._roomWidgetEcho[room.roomId] === {}) delete this._roomWidgetEcho[room.roomId]; + this.emit('updateRoomWidgetEcho'); + } +} + +let singletonWidgetEchoStore = null; +if (!singletonWidgetEchoStore) { + singletonWidgetEchoStore = new WidgetEchoStore(); +} +module.exports = singletonWidgetEchoStore; diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index ab5b5b0130..43ca20e886 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -19,6 +19,7 @@ import MatrixClientPeg from '../MatrixClientPeg'; import SdkConfig from "../SdkConfig"; import dis from '../dispatcher'; import * as url from "url"; +import WidgetEchoStore from '../stores/WidgetEchoStore'; export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room @@ -250,11 +251,16 @@ export default class WidgetUtils { content = {}; } + const room = MatrixClientPeg.get().getRoom(roomId); + WidgetEchoStore.setRoomWidgetEcho(room, widgetId, content); + const client = MatrixClientPeg.get(); // TODO - Room widgets need to be moved to 'm.widget' state events // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => { return WidgetUtils.waitForRoomWidget(widgetId, roomId, addingWidget); + }).finally(() => { + WidgetEchoStore.removeRoomWidgetEcho(room, widgetId); }); }