mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 13:14:58 +08:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
f700c8e60d
@ -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';
|
||||||
import ScalarAuthClient from './ScalarAuthClient';
|
import ScalarAuthClient from './ScalarAuthClient';
|
||||||
|
|
||||||
global.mxCalls = {
|
global.mxCalls = {
|
||||||
@ -431,12 +432,19 @@ async function _startCallApp(roomId, type) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
if (!room) {
|
const currentRoomWidgets = WidgetUtils.getRoomWidgets(room);
|
||||||
console.error("Attempted to start conference call widget in unknown room: " + roomId);
|
|
||||||
|
if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, 'jitsi')) {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
|
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
|
||||||
|
title: _t('Call in Progress'),
|
||||||
|
description: _t('A call is currently being placed!'),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentJitsiWidgets = WidgetUtils.getRoomWidgets(room).filter((ev) => {
|
const currentJitsiWidgets = currentRoomWidgets.filter((ev) => {
|
||||||
return ev.getContent().type === 'jitsi';
|
return ev.getContent().type === 'jitsi';
|
||||||
});
|
});
|
||||||
if (currentJitsiWidgets.length > 0) {
|
if (currentJitsiWidgets.length > 0) {
|
||||||
|
@ -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('update', 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.roomId, WidgetUtils.getRoomWidgets(room));
|
||||||
|
|
||||||
|
return widgets.length > 0 || WidgetEchoStore.roomHasPendingWidgets(room.roomId, WidgetUtils.getRoomWidgets(room));
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
@ -414,6 +425,8 @@ module.exports = React.createClass({
|
|||||||
this._roomStoreToken.remove();
|
this._roomStoreToken.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WidgetEchoStore.removeListener('update', 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();
|
||||||
|
|
||||||
|
@ -325,6 +325,12 @@ export default class AppTile extends React.Component {
|
|||||||
this.props.id,
|
this.props.id,
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
console.error('Failed to delete widget', 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(() => {
|
}).finally(() => {
|
||||||
this.setState({deleting: false});
|
this.setState({deleting: false});
|
||||||
});
|
});
|
||||||
|
@ -29,6 +29,7 @@ import ScalarAuthClient from '../../../ScalarAuthClient';
|
|||||||
import ScalarMessaging from '../../../ScalarMessaging';
|
import ScalarMessaging from '../../../ScalarMessaging';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||||
|
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('update', 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('update', this._updateApps);
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -114,8 +117,11 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_getApps: function() {
|
_getApps: function() {
|
||||||
return WidgetUtils.getRoomWidgets(this.props.room).map((ev) => {
|
const widgets = WidgetEchoStore.getEchoedRoomWidgets(
|
||||||
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.sender, this.props.room.roomId);
|
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
|
||||||
|
);
|
||||||
|
return widgets.map((ev) => {
|
||||||
|
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -200,10 +206,22 @@ module.exports = React.createClass({
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let spinner;
|
||||||
|
if (
|
||||||
|
apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets(
|
||||||
|
this.props.room.roomId,
|
||||||
|
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>
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"Could not connect to the integration server": "Could not connect to the integration server",
|
"Could not connect to the integration server": "Could not connect to the integration server",
|
||||||
"A conference call could not be started because the intgrations server is not available": "A conference call could not be started because the intgrations server is not available",
|
"A conference call could not be started because the intgrations server is not available": "A conference call could not be started because the intgrations server is not available",
|
||||||
"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!",
|
||||||
"Permission Required": "Permission Required",
|
"Permission Required": "Permission Required",
|
||||||
"You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room",
|
"You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room",
|
||||||
@ -695,6 +696,8 @@
|
|||||||
"Delete Widget": "Delete Widget",
|
"Delete Widget": "Delete Widget",
|
||||||
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
|
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
|
||||||
"Delete widget": "Delete widget",
|
"Delete widget": "Delete widget",
|
||||||
|
"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",
|
||||||
"Revoke widget access": "Revoke widget access",
|
"Revoke widget access": "Revoke widget access",
|
||||||
"Minimize apps": "Minimize apps",
|
"Minimize apps": "Minimize apps",
|
||||||
"Reload widget": "Reload widget",
|
"Reload widget": "Reload widget",
|
||||||
|
@ -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.
|
||||||
|
108
src/stores/WidgetEchoStore.js
Normal file
108
src/stores/WidgetEchoStore.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = {
|
||||||
|
// 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]
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @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(roomId, currentRoomWidgets) {
|
||||||
|
const echoedWidgets = [];
|
||||||
|
|
||||||
|
const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]);
|
||||||
|
|
||||||
|
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 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) {
|
||||||
|
echoedWidgets.push(w);
|
||||||
|
}
|
||||||
|
delete roomEchoState[widgetId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return echoedWidgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Object.values(roomEchoState).some((widget) => {
|
||||||
|
return widget.type === type;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roomHasPendingWidgets(roomId, currentRoomWidgets) {
|
||||||
|
return this.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
setRoomWidgetEcho(roomId, widgetId, state) {
|
||||||
|
if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {};
|
||||||
|
|
||||||
|
this._roomWidgetEcho[roomId][widgetId] = state;
|
||||||
|
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('update');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let singletonWidgetEchoStore = null;
|
||||||
|
if (!singletonWidgetEchoStore) {
|
||||||
|
singletonWidgetEchoStore = new WidgetEchoStore();
|
||||||
|
}
|
||||||
|
module.exports = singletonWidgetEchoStore;
|
@ -19,6 +19,11 @@ 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';
|
||||||
|
|
||||||
|
// How long we wait for the state event echo to come back from the server
|
||||||
|
// before waitFor[Room/User]Widget rejects its promise
|
||||||
|
const WIDGET_WAIT_TIME = 20000;
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,7 +160,7 @@ export default class WidgetUtils {
|
|||||||
const timerId = setTimeout(() => {
|
const timerId = setTimeout(() => {
|
||||||
MatrixClientPeg.get().removeListener('accountData', onAccountData);
|
MatrixClientPeg.get().removeListener('accountData', onAccountData);
|
||||||
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
||||||
}, 10000);
|
}, WIDGET_WAIT_TIME);
|
||||||
MatrixClientPeg.get().on('accountData', onAccountData);
|
MatrixClientPeg.get().on('accountData', onAccountData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -208,7 +213,7 @@ export default class WidgetUtils {
|
|||||||
const timerId = setTimeout(() => {
|
const timerId = setTimeout(() => {
|
||||||
MatrixClientPeg.get().removeListener('RoomState.events', onRoomStateEvents);
|
MatrixClientPeg.get().removeListener('RoomState.events', onRoomStateEvents);
|
||||||
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
||||||
}, 10000);
|
}, WIDGET_WAIT_TIME);
|
||||||
MatrixClientPeg.get().on('RoomState.events', onRoomStateEvents);
|
MatrixClientPeg.get().on('RoomState.events', onRoomStateEvents);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -271,11 +276,15 @@ export default class WidgetUtils {
|
|||||||
content = {};
|
content = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WidgetEchoStore.setRoomWidgetEcho(roomId, 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(roomId, widgetId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user