From 8b64ddcbe83598de0d2cd568ca333bc861a8a8a8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Jul 2018 11:16:44 +0100 Subject: [PATCH 01/18] Do some level of local echo for widgets * Show a spinner while we wait for widgets to be deleted * Hide widgets while they're pending deletion * Don't put another jitsi widget into the room if there's already one pending --- src/CallHandler.js | 25 +++-- src/actions/MatrixActionCreators.js | 15 +++ src/components/structures/RoomView.js | 15 ++- src/components/views/rooms/AppsDrawer.js | 23 ++++- src/i18n/strings/en_EN.json | 121 +++++++++-------------- src/stores/RoomViewStore.js | 2 +- src/stores/WidgetEchoStore.js | 112 +++++++++++++++++++++ src/utils/WidgetUtils.js | 6 ++ 8 files changed, 237 insertions(+), 82 deletions(-) create mode 100644 src/stores/WidgetEchoStore.js 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); }); } From 3199e6857876aa9d269bc8064ef90eddf8320b59 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Jul 2018 11:22:19 +0100 Subject: [PATCH 02/18] Lint --- src/stores/WidgetEchoStore.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index 26d24c0563..f117b37e95 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -16,9 +16,6 @@ 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. @@ -40,6 +37,10 @@ class WidgetEchoStore extends EventEmitter { * 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. + * + * @param {Room} room The room object to get widgets for + * @param {MatrixEvent[]} currentRoomWidgets Current widgets for the room + * @returns {MatrixEvent[]} List of widgets in the room, minus any pending removal */ getEchoedRoomWidgets(room, currentRoomWidgets) { const echoedWidgets = []; @@ -82,7 +83,7 @@ class WidgetEchoStore extends EventEmitter { return Object.keys(roomEchoState).length > 0; } else { return Object.values(roomEchoState).some((widget) => { - return widget.type === type; + return widget.type === type; }); } } From 9d41b87678c49cd999b9668071fe10c9ff78ca15 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Jul 2018 11:27:15 +0100 Subject: [PATCH 03/18] i18n --- src/i18n/strings/en_EN.json | 74 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ae4ca74085..b73cf037c8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -149,50 +149,50 @@ "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.", + "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", + "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", + "%(senderName)s requested a VoIP conference.": "%(senderName)s requested a VoIP conference.", + "%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.", + "%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", + "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.", + "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).", + "%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.", + "%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", + "%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.", "VoIP conference started.": "VoIP conference started.", - " joined the room.": " joined the room.", + "%(targetName)s joined the room.": "%(targetName)s joined the room.", "VoIP conference finished.": "VoIP conference finished.", - " 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.", + "%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.", + "%(targetName)s left the room.": "%(targetName)s left the room.", + "%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", + "%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", + "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".", + "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", + "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.", + "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", "Someone": "Someone", "(not supported by this browser)": "(not supported by this browser)", - " answered the call.": " answered the call.", + "%(senderName)s answered the call.": "%(senderName)s 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 ", + "%(senderName)s ended the call.": "%(senderName)s ended the call.", + "%(senderName)s placed a %(callType)s call.": "%(senderName)s placed a %(callType)s call.", + "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", + "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", + "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).", + "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", + "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", + "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", + "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", + "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", "%(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", From c665ab8a22f02458a611bd41fb9b524d8f5f32de Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Jul 2018 11:55:41 +0100 Subject: [PATCH 04/18] Add error dialog if widget remove fails Also up the timeout because matrix.org is that slow --- src/components/views/elements/AppTile.js | 6 ++++++ src/i18n/strings/en_EN.json | 2 ++ src/utils/WidgetUtils.js | 7 +++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 7b69057e3e..1eaa1837ed 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -325,6 +325,12 @@ export default class AppTile extends React.Component { this.props.id, ).catch((e) => { console.error('Failed to delete widget', e); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + + Modal.createTrackedDialog('Failed to remove widget', '', ErrorDialog, { + title: _t('Failed to remove widget'), + description: _t('An error ocurred whilst trying to remove the widget from the room'), + }); }).finally(() => { this.setState({deleting: false}); }); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b73cf037c8..2cab44fafb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -205,6 +205,8 @@ "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?", + "Failed to remove widget": "Failed to remove widget", + "An error ocurred whilst trying to remove the widget from the room": "An error ocurred whilst trying to remove the widget from the room", "Failed to join room": "Failed to join room", "Message Pinning": "Message Pinning", "Jitsi Conference Calling": "Jitsi Conference Calling", diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 43ca20e886..4effa21fd6 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -21,6 +21,9 @@ import dis from '../dispatcher'; import * as url from "url"; import WidgetEchoStore from '../stores/WidgetEchoStore'; +// How long we wait for the state event echo to come back from the server +const WIDGET_WAIT_TIME = 20000; + export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room * (Does not apply to non-room-based / user widgets) @@ -135,7 +138,7 @@ export default class WidgetUtils { const timerId = setTimeout(() => { MatrixClientPeg.get().removeListener('accountData', onAccountData); reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear")); - }, 10000); + }, WIDGET_WAIT_TIME); MatrixClientPeg.get().on('accountData', onAccountData); }); } @@ -188,7 +191,7 @@ export default class WidgetUtils { const timerId = setTimeout(() => { MatrixClientPeg.get().removeListener('RoomState.events', onRoomStateEvents); reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear")); - }, 10000); + }, WIDGET_WAIT_TIME); MatrixClientPeg.get().on('RoomState.events', onRoomStateEvents); }); } From 5f2e2efce6afbc6372295986aeb3d9a195987268 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 18:22:05 +0100 Subject: [PATCH 05/18] Revert 8b64ddc for MatrixActionCreators As it wasn't used in the end --- src/actions/MatrixActionCreators.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js index d8c315f505..6e1d52a88f 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.js @@ -183,20 +183,6 @@ 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. @@ -218,7 +204,6 @@ 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); }, /** From eb552e5cefc39f46ae3429e25d3efd4efbbc877f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 18:43:20 +0100 Subject: [PATCH 06/18] Just pass the roomId into WidgetEchoStore --- src/CallHandler.js | 2 +- src/components/structures/RoomView.js | 4 ++-- src/components/views/rooms/AppsDrawer.js | 4 ++-- src/stores/WidgetEchoStore.js | 26 ++++++++++++------------ src/utils/WidgetUtils.js | 4 ++-- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index abbd407e7f..7529171aa2 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -416,7 +416,7 @@ function _startCallApp(roomId, type) { const currentRoomWidgets = WidgetUtils.getRoomWidgets(room); - if (WidgetEchoStore.roomHasPendingWidgetsOfType(room, currentRoomWidgets, 'jitsi')) { + if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, 'jitsi')) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Already have pending Jitsi Widget', '', ErrorDialog, { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 5c97b5c29a..1850806520 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -328,9 +328,9 @@ module.exports = React.createClass({ return false; } - const widgets = WidgetEchoStore.getEchoedRoomWidgets(room, WidgetUtils.getRoomWidgets(room)); + const widgets = WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)); - return widgets.length > 0 || WidgetEchoStore.roomHasPendingWidgets(room, WidgetUtils.getRoomWidgets(room)); + return widgets.length > 0 || WidgetEchoStore.roomHasPendingWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)); }, componentDidMount: function() { diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 64aa8fadc8..cdaa2d2b1a 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -167,7 +167,7 @@ module.exports = React.createClass({ _getApps: function() { const widgets = WidgetEchoStore.getEchoedRoomWidgets( - this.props.room, WidgetUtils.getRoomWidgets(this.props.room), + this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), ); return widgets.map((ev) => { return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender); @@ -260,7 +260,7 @@ module.exports = React.createClass({ let spinner; if ( apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets( - this.props.room, + this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), ) ) { diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index f117b37e95..f50d0fd962 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -38,14 +38,14 @@ class WidgetEchoStore extends EventEmitter { * and we don't really need the actual widget events anyway since we just want to * show a spinner / prevent widgets being added twice. * - * @param {Room} room The room object to get widgets for + * @param {Room} roomId The ID of the room to get widgets for * @param {MatrixEvent[]} currentRoomWidgets Current widgets for the room * @returns {MatrixEvent[]} List of widgets in the room, minus any pending removal */ - getEchoedRoomWidgets(room, currentRoomWidgets) { + getEchoedRoomWidgets(roomId, currentRoomWidgets) { const echoedWidgets = []; - const roomEchoState = Object.assign({}, this._roomWidgetEcho[room.roomId]); + const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]); for (const w of currentRoomWidgets) { const widgetId = w.getStateKey(); @@ -70,8 +70,8 @@ class WidgetEchoStore extends EventEmitter { return echoedWidgets; } - roomHasPendingWidgetsOfType(room, currentRoomWidgets, type) { - const roomEchoState = Object.assign({}, this._roomWidgetEcho[room.roomId]); + roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type) { + const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]); if (roomEchoState === undefined) return false; for (const w of currentRoomWidgets) { @@ -88,20 +88,20 @@ class WidgetEchoStore extends EventEmitter { } } - roomHasPendingWidgets(room, currentRoomWidgets) { - return this.roomHasPendingWidgetsOfType(room, currentRoomWidgets); + roomHasPendingWidgets(roomId, currentRoomWidgets) { + return this.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets); } - setRoomWidgetEcho(room, widgetId, state) { - if (this._roomWidgetEcho[room.roomId] === undefined) this._roomWidgetEcho[room.roomId] = {}; + setRoomWidgetEcho(roomId, widgetId, state) { + if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {}; - this._roomWidgetEcho[room.roomId][widgetId] = state; + this._roomWidgetEcho[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]; + removeRoomWidgetEcho(roomId, widgetId) { + delete this._roomWidgetEcho[roomId][widgetId]; + if (this._roomWidgetEcho[roomId] === {}) delete this._roomWidgetEcho[roomId]; this.emit('updateRoomWidgetEcho'); } } diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 4effa21fd6..7bd50d063b 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -255,7 +255,7 @@ export default class WidgetUtils { } const room = MatrixClientPeg.get().getRoom(roomId); - WidgetEchoStore.setRoomWidgetEcho(room, widgetId, content); + WidgetEchoStore.setRoomWidgetEcho(roomId, widgetId, content); const client = MatrixClientPeg.get(); // TODO - Room widgets need to be moved to 'm.widget' state events @@ -263,7 +263,7 @@ export default class WidgetUtils { return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => { return WidgetUtils.waitForRoomWidget(widgetId, roomId, addingWidget); }).finally(() => { - WidgetEchoStore.removeRoomWidgetEcho(room, widgetId); + WidgetEchoStore.removeRoomWidgetEcho(roomId, widgetId); }); } From 1f2d279a0a8eee0beece47b5e154829de4088398 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:38:42 +0100 Subject: [PATCH 07/18] Consistency, redundant check & doc --- src/stores/WidgetEchoStore.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index f50d0fd962..3c2f79e951 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -25,6 +25,7 @@ class WidgetEchoStore extends EventEmitter { super(); this._roomWidgetEcho = { + // Map as below. For widgets that have been deleted locally, the object is empty. // roomId: { // widgetId: [object] // } @@ -49,7 +50,7 @@ class WidgetEchoStore extends EventEmitter { for (const w of currentRoomWidgets) { const widgetId = w.getStateKey(); - if (roomEchoState && roomEchoState[widgetId] && Object.keys(roomEchoState[widgetId]).length === 0) { + if (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 @@ -101,7 +102,7 @@ class WidgetEchoStore extends EventEmitter { removeRoomWidgetEcho(roomId, widgetId) { delete this._roomWidgetEcho[roomId][widgetId]; - if (this._roomWidgetEcho[roomId] === {}) delete this._roomWidgetEcho[roomId]; + if (Object.keys(this._roomWidgetEcho[roomId]).length === 0) delete this._roomWidgetEcho[roomId]; this.emit('updateRoomWidgetEcho'); } } From fb7ce9dd7fb1e8f76d3b905e102faa7568df2b69 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:40:45 +0100 Subject: [PATCH 08/18] More doc --- src/stores/WidgetEchoStore.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index 3c2f79e951..493eee7181 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -25,7 +25,8 @@ class WidgetEchoStore extends EventEmitter { super(); this._roomWidgetEcho = { - // Map as below. For widgets that have been deleted locally, the object is empty. + // Map as below. Object is the content of the widget state event, + // so for widgets that have been deleted locally, the object is empty. // roomId: { // widgetId: [object] // } From 7e2b559ccd9ca812d2c6e4955940016d044e067e Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:46:37 +0100 Subject: [PATCH 09/18] Make if clearer --- src/stores/WidgetEchoStore.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index 493eee7181..e4fc6d755a 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -51,12 +51,9 @@ class WidgetEchoStore extends EventEmitter { for (const w of currentRoomWidgets) { const widgetId = w.getStateKey(); - if (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]); + // If there's no echo, or the echo still has a widget present, show the *old* widget + // we don't include widgets that have changed for the same rason we don't include new ones + if (!roomEchoState[widgetId] || Object.keys(roomEchoState[widgetId]).length !== 0) { } else { echoedWidgets.push(w); } From 8c10dc18607df68d0ec21e4932fd3e016330d406 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:50:11 +0100 Subject: [PATCH 10/18] Remove unhelpful commented code --- src/stores/WidgetEchoStore.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index e4fc6d755a..3d6f03c459 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -60,12 +60,6 @@ class WidgetEchoStore extends EventEmitter { 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; } From 3f60300983b966a01d5158da4ae30f060f7c481c Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:51:12 +0100 Subject: [PATCH 11/18] Remove redundant check --- src/stores/WidgetEchoStore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index 3d6f03c459..2710f0a9a1 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -65,7 +65,6 @@ class WidgetEchoStore extends EventEmitter { roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type) { const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]); - if (roomEchoState === undefined) return false; for (const w of currentRoomWidgets) { const widgetId = w.getStateKey(); From 658ac73064227f21eaf9b3037a3c402ba3846c31 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:57:24 +0100 Subject: [PATCH 12/18] comments --- src/stores/WidgetEchoStore.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index 2710f0a9a1..cce404662e 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -66,11 +66,14 @@ class WidgetEchoStore extends EventEmitter { roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type) { const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]); + // any widget IDs that are already in the room are not pending, so + // echoes for them don't count as pending. for (const w of currentRoomWidgets) { const widgetId = w.getStateKey(); delete roomEchoState[widgetId]; } + // if there's anything left then there are pending widgets. if (type === undefined) { return Object.keys(roomEchoState).length > 0; } else { From c26b300f301c9c414fe9887723c60010e81de77f Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:58:10 +0100 Subject: [PATCH 13/18] more comments --- src/utils/WidgetUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 7bd50d063b..cf12a64329 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -22,6 +22,7 @@ import * as url from "url"; import WidgetEchoStore from '../stores/WidgetEchoStore'; // How long we wait for the state event echo to come back from the server +// before the promise is rejected const WIDGET_WAIT_TIME = 20000; export default class WidgetUtils { From 3f20eb96105e4f412b9fdb44228292e4e5ffea79 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 19:58:54 +0100 Subject: [PATCH 14/18] Use more helpful dialog tracking title --- src/CallHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 7529171aa2..d61ea93c02 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -419,7 +419,7 @@ function _startCallApp(roomId, type) { if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, 'jitsi')) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Already have pending Jitsi Widget', '', ErrorDialog, { + Modal.createTrackedDialog('Call being placed', '', ErrorDialog, { title: _t('Call in Progress'), description: _t('A call is currently being placed!'), }); From b6f854abe494a33d1f369ca46abb2d48dd3cfa37 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Jul 2018 20:00:42 +0100 Subject: [PATCH 15/18] Simplify event name as it's the only one --- src/components/structures/RoomView.js | 4 ++-- src/components/views/rooms/AppsDrawer.js | 4 ++-- src/stores/WidgetEchoStore.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 1850806520..6b183c2601 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -155,7 +155,7 @@ module.exports = React.createClass({ this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._onRoomViewStoreUpdate(true); - WidgetEchoStore.on('updateRoomWidgetEcho', this._onWidgetEchoStoreUpdate); + WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate); }, _onRoomViewStoreUpdate: function(initial) { @@ -425,7 +425,7 @@ module.exports = React.createClass({ this._roomStoreToken.remove(); } - WidgetEchoStore.removeListener('updateRoomWidgetEcho', this._onWidgetEchoStoreUpdate); + WidgetEchoStore.removeListener('update', 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 cdaa2d2b1a..3b140729f1 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -58,7 +58,7 @@ module.exports = React.createClass({ componentWillMount: function() { ScalarMessaging.startListening(); MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); - WidgetEchoStore.on('updateRoomWidgetEcho', this._updateApps); + WidgetEchoStore.on('update', this._updateApps); }, componentDidMount: function() { @@ -84,7 +84,7 @@ module.exports = React.createClass({ if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); } - WidgetEchoStore.removeListener('updateRoomWidgetEcho', this._updateApps); + WidgetEchoStore.removeListener('update', this._updateApps); dis.unregister(this.dispatcherRef); }, diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index cce404662e..f442baee6c 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -91,13 +91,13 @@ class WidgetEchoStore extends EventEmitter { if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {}; this._roomWidgetEcho[roomId][widgetId] = state; - this.emit('updateRoomWidgetEcho'); + this.emit('update'); } removeRoomWidgetEcho(roomId, widgetId) { delete this._roomWidgetEcho[roomId][widgetId]; if (Object.keys(this._roomWidgetEcho[roomId]).length === 0) delete this._roomWidgetEcho[roomId]; - this.emit('updateRoomWidgetEcho'); + this.emit('update'); } } From 0f32c3a01852f228607ed3b586020fd3dffc1a6d Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 16 Jul 2018 13:18:10 +0100 Subject: [PATCH 16/18] PR feedback --- src/CallHandler.js | 2 +- src/stores/WidgetEchoStore.js | 4 ++-- src/utils/WidgetUtils.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index d61ea93c02..a9c36c8af2 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -419,7 +419,7 @@ function _startCallApp(roomId, type) { if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, 'jitsi')) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Call being placed', '', ErrorDialog, { + Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, { title: _t('Call in Progress'), description: _t('A call is currently being placed!'), }); diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index f442baee6c..0d14ed1d60 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -52,9 +52,9 @@ class WidgetEchoStore extends EventEmitter { for (const w of currentRoomWidgets) { const widgetId = w.getStateKey(); // If there's no echo, or the echo still has a widget present, show the *old* widget - // we don't include widgets that have changed for the same rason we don't include new ones + // we don't include widgets that have changed for the same reason we don't include new ones, + // ie. we'd need to fake matrix events to do so and therte's currently no need. if (!roomEchoState[widgetId] || Object.keys(roomEchoState[widgetId]).length !== 0) { - } else { echoedWidgets.push(w); } delete roomEchoState[widgetId]; diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index cf12a64329..5d98e6f45c 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -22,7 +22,7 @@ import * as url from "url"; import WidgetEchoStore from '../stores/WidgetEchoStore'; // How long we wait for the state event echo to come back from the server -// before the promise is rejected +// before waitFor[Room/User]Widget rejects its promise const WIDGET_WAIT_TIME = 20000; export default class WidgetUtils { From 983dc3ad2e8902c0408b34df7eff5468d04a36fe Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 16 Jul 2018 16:19:18 +0100 Subject: [PATCH 17/18] lint --- src/utils/WidgetUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 5d98e6f45c..c82bf25ed0 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -255,7 +255,6 @@ export default class WidgetUtils { content = {}; } - const room = MatrixClientPeg.get().getRoom(roomId); WidgetEchoStore.setRoomWidgetEcho(roomId, widgetId, content); const client = MatrixClientPeg.get(); From 68c46a694e625f7dad0ff023f0253db09a30e150 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 18 Jul 2018 10:18:55 +0100 Subject: [PATCH 18/18] lint --- src/components/views/rooms/AppsDrawer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 0de0b132e9..e6fe445b45 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -29,7 +29,6 @@ import ScalarAuthClient from '../../../ScalarAuthClient'; 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