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
This commit is contained in:
David Baker 2018-07-03 11:16:44 +01:00
parent 767e67dc70
commit 8b64ddcbe8
8 changed files with 237 additions and 82 deletions

View file

@ -62,6 +62,7 @@ import dis from './dispatcher';
import { showUnknownDeviceDialogForCalls } from './cryptodevices'; import { showUnknownDeviceDialogForCalls } from './cryptodevices';
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import WidgetUtils from './utils/WidgetUtils'; import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore';
global.mxCalls = { global.mxCalls = {
//room_id: MatrixCall //room_id: MatrixCall
@ -402,18 +403,30 @@ function _onAction(payload) {
} }
function _startCallApp(roomId, type) { function _startCallApp(roomId, type) {
dis.dispatch({
action: 'appsDrawer',
show: true,
});
const room = MatrixClientPeg.get().getRoom(roomId); const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) { if (!room) {
console.error("Attempted to start conference call widget in unknown room: " + roomId); console.error("Attempted to start conference call widget in unknown room: " + roomId);
return; 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'; return ev.getContent().type === 'jitsi';
}); });
if (currentJitsiWidgets.length > 0) { if (currentJitsiWidgets.length > 0) {

View file

@ -183,6 +183,20 @@ function createEventDecryptedAction(matrixClient, event) {
return { action: 'MatrixActions.Event.decrypted', 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 * This object is responsible for dispatching actions when certain events are emitted by
* the given MatrixClient. * the given MatrixClient.
@ -204,6 +218,7 @@ export default {
this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction);
this._addMatrixClientListener(matrixClient, 'RoomMember.membership', createRoomMembershipAction); this._addMatrixClientListener(matrixClient, 'RoomMember.membership', createRoomMembershipAction);
this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction); this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction);
this._addMatrixClientListener(matrixClient, 'RoomState.events', createRoomStateEventsAction);
}, },
/** /**

View file

@ -45,6 +45,7 @@ import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import RoomViewStore from '../../stores/RoomViewStore'; import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
import WidgetUtils from '../../utils/WidgetUtils'; import WidgetUtils from '../../utils/WidgetUtils';
@ -153,6 +154,8 @@ module.exports = React.createClass({
// Start listening for RoomViewStore updates // Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true); this._onRoomViewStoreUpdate(true);
WidgetEchoStore.on('updateRoomWidgetEcho', this._onWidgetEchoStoreUpdate);
}, },
_onRoomViewStoreUpdate: function(initial) { _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) { _setupRoom: function(room, roomId, joining, shouldPeek) {
// if this is an unknown room then we're in one of three states: // 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) // - This is a room we can peek into (search engine) (we can /peek)
@ -319,7 +328,9 @@ module.exports = React.createClass({
return false; 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() { componentDidMount: function() {
@ -414,6 +425,8 @@ module.exports = React.createClass({
this._roomStoreToken.remove(); this._roomStoreToken.remove();
} }
WidgetEchoStore.removeListener('updateRoomWidgetEcho', this._onWidgetEchoStoreUpdate);
// cancel any pending calls to the rate_limited_funcs // cancel any pending calls to the rate_limited_funcs
this._updateRoomMembers.cancelPendingCall(); this._updateRoomMembers.cancelPendingCall();

View file

@ -29,6 +29,7 @@ import ScalarMessaging from '../../../ScalarMessaging';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetUtils from '../../../utils/WidgetUtils';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import WidgetEchoStore from "../../../stores/WidgetEchoStore";
// The maximum number of widgets that can be added in a room // The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2; const MAX_WIDGETS = 2;
@ -57,6 +58,7 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
ScalarMessaging.startListening(); ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('updateRoomWidgetEcho', this._updateApps);
}, },
componentDidMount: function() { componentDidMount: function() {
@ -82,6 +84,7 @@ module.exports = React.createClass({
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
} }
WidgetEchoStore.removeListener('updateRoomWidgetEcho', this._updateApps);
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
}, },
@ -163,7 +166,10 @@ module.exports = React.createClass({
}, },
_getApps: function() { _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); return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
}); });
}, },
@ -231,7 +237,8 @@ module.exports = React.createClass({
waitForIframeLoad={app.waitForIframeLoad} waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={enableScreenshots ? ["m.capability.screenshot"] : []} whitelistCapabilities={enableScreenshots ? ["m.capability.screenshot"] : []}
/>); />);
}); },
);
let addWidget; let addWidget;
if (this.props.showApps && if (this.props.showApps &&
@ -250,10 +257,22 @@ module.exports = React.createClass({
</div>; </div>;
} }
let spinner;
if (
apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets(
this.props.room,
WidgetUtils.getRoomWidgets(this.props.room),
)
) {
const Loader = sdk.getComponent("elements.Spinner");
spinner = <Loader />;
}
return ( return (
<div className={'mx_AppsDrawer' + (this.props.hide ? ' mx_AppsDrawer_hidden' : '')}> <div className={'mx_AppsDrawer' + (this.props.hide ? ' mx_AppsDrawer_hidden' : '')}>
<div id='apps' className='mx_AppsContainer'> <div id='apps' className='mx_AppsContainer'>
{ apps } { apps }
{ spinner }
</div> </div>
{ this._canUserModify() && addWidget } { this._canUserModify() && addWidget }
</div> </div>

View file

@ -40,14 +40,11 @@
"Failed to set up conference call": "Failed to set up conference call", "Failed to set up conference call": "Failed to set up conference call",
"Conference call failed.": "Conference call failed.", "Conference call failed.": "Conference call failed.",
"Call in Progress": "Call in Progress", "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!", "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' 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", "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", "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", "Sun": "Sun",
"Mon": "Mon", "Mon": "Mon",
"Tue": "Tue", "Tue": "Tue",
@ -87,7 +84,6 @@
"Failed to invite users to community": "Failed to invite users to community", "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 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:", "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 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", "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", "Unable to enable Notifications": "Unable to enable Notifications",
@ -153,18 +149,59 @@
"Displays action": "Displays action", "Displays action": "Displays action",
"Unrecognised command:": "Unrecognised command:", "Unrecognised command:": "Unrecognised command:",
"Reason": "Reason", "Reason": "Reason",
"<target> accepted the invitation for %(displayName)s.": "<target> accepted the invitation for %(displayName)s.",
"<target> accepted an invitation.": "<target> accepted an invitation.",
"<sender> requested a VoIP conference.": "<sender> requested a VoIP conference.",
"<sender> invited <target>.": "<sender> invited <target>.",
"<sender> banned <target>.": "<sender> banned <target>.",
"<oldDisplayName> changed their display name to <displayName>.": "<oldDisplayName> changed their display name to <displayName>.",
"<sender> set their display name to <displayName>.": "<sender> set their display name to <displayName>.",
"<sender> removed their display name (<oldDisplayName>).": "<sender> removed their display name (<oldDisplayName>).",
"<sender> removed their profile picture.": "<sender> removed their profile picture.",
"<sender> changed their profile picture.": "<sender> changed their profile picture.",
"<sender> set a profile picture.": "<sender> set a profile picture.",
"VoIP conference started.": "VoIP conference started.", "VoIP conference started.": "VoIP conference started.",
"<target> joined the room.": "<target> joined the room.",
"VoIP conference finished.": "VoIP conference finished.", "VoIP conference finished.": "VoIP conference finished.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", "<target> rejected the invitation.": "<target> rejected the invitation.",
"<target> left the room.": "<target> left the room.",
"<sender> unbanned <target>.": "<sender> unbanned <target>.",
"<sender> kicked <target>.": "<sender> kicked <target>.",
"<sender> withdrew <target>'s invitation.": "<sender> withdrew <target>'s invitation.",
"<sender> changed the topic to \"%(topic)s\".": "<sender> changed the topic to \"%(topic)s\".",
"<sender> removed the room name.": "<sender> removed the room name.",
"<sender> changed the room name to %(roomName)s.": "<sender> changed the room name to %(roomName)s.",
"<sender> sent an image.": "<sender> sent an image.",
"Someone": "Someone", "Someone": "Someone",
"(not supported by this browser)": "(not supported by this browser)", "(not supported by this browser)": "(not supported by this browser)",
"<sender> answered the call.": "<sender> answered the call.",
"(could not connect media)": "(could not connect media)", "(could not connect media)": "(could not connect media)",
"(no answer)": "(no answer)", "(no answer)": "(no answer)",
"(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)", "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)",
"<sender> ended the call.": "<sender> ended the call.",
"<sender> placed a %(callType)s call.": "<sender> placed a %(callType)s call.",
"<sender> sent an invitation to %(targetDisplayName)s to join the room.": "<sender> sent an invitation to %(targetDisplayName)s to join the room.",
"<sender> made future room history visible to all room members, from the point they are invited.": "<sender> made future room history visible to all room members, from the point they are invited.",
"<sender> made future room history visible to all room members, from the point they joined.": "<sender> made future room history visible to all room members, from the point they joined.",
"<sender> made future room history visible to all room members.": "<sender> made future room history visible to all room members.",
"<sender> made future room history visible to anyone.": "<sender> made future room history visible to anyone.",
"<sender> made future room history visible to unknown (%(visibility)s).": "<sender> made future room history visible to unknown (%(visibility)s).",
"<sender> turned on end-to-end encryption (algorithm %(algorithm)s).": "<sender> turned on end-to-end encryption (algorithm %(algorithm)s).",
"<user> from %(fromPowerLevel)s to %(toPowerLevel)s": "<user> from %(fromPowerLevel)s to %(toPowerLevel)s",
"<sender> changed the power level of %(powerLevelDiffText)s.": "<sender> changed the power level of %(powerLevelDiffText)s.",
"<sender> changed the pinned messages for the room.": "<sender> changed the pinned messages for the room.",
"%(widgetName)s widget modified by <sender>": "%(widgetName)s widget modified by <sender>",
"%(widgetName)s widget added by <sender>": "%(widgetName)s widget added by <sender>",
"%(widgetName)s widget removed by <sender>": "%(widgetName)s widget removed by <sender>",
"%(displayName)s is typing": "%(displayName)s is typing", "%(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|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 %(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", "%(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", "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", "Not a valid Riot keyfile": "Not a valid Riot keyfile",
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
@ -277,29 +314,6 @@
"Off": "Off", "Off": "Off",
"On": "On", "On": "On",
"Noisy": "Noisy", "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 <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> 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", "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.", "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", "Add a widget": "Add a widget",
@ -401,11 +415,11 @@
"numbullet": "numbullet", "numbullet": "numbullet",
"Markdown is disabled": "Markdown is disabled", "Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled", "Markdown is enabled": "Markdown is enabled",
"Unpin Message": "Unpin Message",
"Jump to message": "Jump to message",
"No pinned messages.": "No pinned messages.", "No pinned messages.": "No pinned messages.",
"Loading...": "Loading...", "Loading...": "Loading...",
"Pinned Messages": "Pinned Messages", "Pinned Messages": "Pinned Messages",
"Unpin Message": "Unpin Message",
"Jump to message": "Jump to message",
"%(duration)ss": "%(duration)ss", "%(duration)ss": "%(duration)ss",
"%(duration)sm": "%(duration)sm", "%(duration)sm": "%(duration)sm",
"%(duration)sh": "%(duration)sh", "%(duration)sh": "%(duration)sh",
@ -580,6 +594,9 @@
"Invalid file%(extra)s": "Invalid file%(extra)s", "Invalid file%(extra)s": "Invalid file%(extra)s",
"Error decrypting image": "Error decrypting image", "Error decrypting image": "Error decrypting image",
"Error decrypting video": "Error decrypting video", "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 <img/>": "%(senderDisplayName)s changed the room avatar to <img/>",
"Copied!": "Copied!", "Copied!": "Copied!",
"Failed to copy": "Failed to copy", "Failed to copy": "Failed to copy",
"Add an Integration": "Add an Integration", "Add an Integration": "Add an Integration",
@ -755,7 +772,6 @@
"Room directory": "Room directory", "Room directory": "Room directory",
"Start chat": "Start chat", "Start chat": "Start chat",
"And %(count)s more...|other": "And %(count)s more...", "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", "ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User", "Add User": "Add User",
"Matrix ID": "Matrix ID", "Matrix ID": "Matrix ID",
@ -1193,44 +1209,5 @@
"Import": "Import", "Import": "Import",
"Failed to set direct chat tag": "Failed to set direct chat tag", "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 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", "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"
"<target> accepted the invitation for %(displayName)s.": "<target> accepted the invitation for %(displayName)s.",
"<target> accepted an invitation.": "<target> accepted an invitation.",
"<sender> requested a VoIP conference.": "<sender> requested a VoIP conference.",
"<sender> invited <target>.": "<sender> invited <target>.",
"<sender> banned <target>.": "<sender> banned <target>.",
"<oldDisplayName> changed their display name to <displayName>.": "<oldDisplayName> changed their display name to <displayName>.",
"<sender> set their display name to <displayName>.": "<sender> set their display name to <displayName>.",
"<sender> removed their display name (<oldDisplayName>).": "<sender> removed their display name (<oldDisplayName>).",
"<sender> removed their profile picture.": "<sender> removed their profile picture.",
"<sender> changed their profile picture.": "<sender> changed their profile picture.",
"<sender> set a profile picture.": "<sender> set a profile picture.",
"<target> joined the room.": "<target> joined the room.",
"<target> rejected the invitation.": "<target> rejected the invitation.",
"<target> left the room.": "<target> left the room.",
"<sender> unbanned <target>.": "<sender> unbanned <target>.",
"<sender> kicked <target>.": "<sender> kicked <target>.",
"<sender> withdrew <target>'s invitation.": "<sender> withdrew <target>'s invitation.",
"<sender> changed the topic to \"%(topic)s\".": "<sender> changed the topic to \"%(topic)s\".",
"<sender> changed the room name to %(roomName)s.": "<sender> changed the room name to %(roomName)s.",
"<sender> changed the avatar for %(roomName)s": "<sender> changed the avatar for %(roomName)s",
"<sender> changed the room avatar to <img/>": "<sender> changed the room avatar to <img/>",
"<sender> removed the room name.": "<sender> removed the room name.",
"<sender> removed the room avatar.": "<sender> removed the room avatar.",
"<sender> answered the call.": "<sender> answered the call.",
"<sender> ended the call.": "<sender> ended the call.",
"<sender> placed a %(callType)s call.": "<sender> placed a %(callType)s call.",
"<sender> sent an invitation to %(targetDisplayName)s to join the room.": "<sender> sent an invitation to %(targetDisplayName)s to join the room.",
"<sender> made future room history visible to all room members, from the point they are invited.": "<sender> made future room history visible to all room members, from the point they are invited.",
"<sender> made future room history visible to all room members, from the point they joined.": "<sender> made future room history visible to all room members, from the point they joined.",
"<sender> made future room history visible to all room members.": "<sender> made future room history visible to all room members.",
"<sender> made future room history visible to anyone.": "<sender> made future room history visible to anyone.",
"<sender> made future room history visible to unknown (%(visibility)s).": "<sender> made future room history visible to unknown (%(visibility)s).",
"<sender> turned on end-to-end encryption (algorithm %(algorithm)s).": "<sender> turned on end-to-end encryption (algorithm %(algorithm)s).",
"<user> from %(fromPowerLevel)s to %(toPowerLevel)s": "<user> from %(fromPowerLevel)s to %(toPowerLevel)s",
"<sender> changed the power level of %(powerLevelDiffText)s.": "<sender> changed the power level of %(powerLevelDiffText)s.",
"<sender> changed the pinned messages for the room.": "<sender> changed the pinned messages for the room.",
"%(widgetName)s widget modified by <sender>": "%(widgetName)s widget modified by <sender>",
"%(widgetName)s widget added by <sender>": "%(widgetName)s widget added by <sender>",
"%(widgetName)s widget removed by <sender>": "%(widgetName)s widget removed by <sender>"
} }

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Vector Creations Ltd 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View file

@ -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;

View file

@ -19,6 +19,7 @@ import MatrixClientPeg from '../MatrixClientPeg';
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import dis from '../dispatcher'; import dis from '../dispatcher';
import * as url from "url"; import * as url from "url";
import WidgetEchoStore from '../stores/WidgetEchoStore';
export default class WidgetUtils { export default class WidgetUtils {
/* Returns true if user is able to send state events to modify widgets in this room /* 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 = {}; content = {};
} }
const room = MatrixClientPeg.get().getRoom(roomId);
WidgetEchoStore.setRoomWidgetEcho(room, widgetId, content);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
// TODO - Room widgets need to be moved to 'm.widget' state events // TODO - Room widgets need to be moved to 'm.widget' state events
// https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => { return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => {
return WidgetUtils.waitForRoomWidget(widgetId, roomId, addingWidget); return WidgetUtils.waitForRoomWidget(widgetId, roomId, addingWidget);
}).finally(() => {
WidgetEchoStore.removeRoomWidgetEcho(room, widgetId);
}); });
} }