Merge branch 'develop' into wmwragg/one-to-one-chat
This commit is contained in:
commit
9c0f51fb82
15 changed files with 285 additions and 112 deletions
|
@ -23,6 +23,10 @@ var linkify = require('linkifyjs');
|
|||
var linkifyElement = require('linkifyjs/element');
|
||||
var linkifyMatrix = require('../../../linkify-matrix');
|
||||
var sdk = require('../../../index');
|
||||
var ScalarAuthClient = require("../../../ScalarAuthClient");
|
||||
var Modal = require("../../../Modal");
|
||||
var SdkConfig = require('../../../SdkConfig');
|
||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
|
@ -176,15 +180,66 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onStarterLinkClick: function(starterLink, ev) {
|
||||
ev.preventDefault();
|
||||
// We need to add on our scalar token to the starter link, but we may not have one!
|
||||
// In addition, we can't fetch one on click and then go to it immediately as that
|
||||
// is then treated as a popup!
|
||||
// We can get around this by fetching one now and showing a "confirmation dialog" (hurr hurr)
|
||||
// which requires the user to click through and THEN we can open the link in a new tab because
|
||||
// the window.open command occurs in the same stack frame as the onClick callback.
|
||||
|
||||
let integrationsEnabled = UserSettingsStore.isFeatureEnabled("integration_management");
|
||||
if (!integrationsEnabled) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Integrations disabled",
|
||||
description: "You need to enable the Labs option 'Integrations Management' in your Vector user settings first.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Go fetch a scalar token
|
||||
let scalarClient = new ScalarAuthClient();
|
||||
scalarClient.connect().then(() => {
|
||||
let completeUrl = scalarClient.getStarterLink(starterLink);
|
||||
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
let integrationsUrl = SdkConfig.get().integrations_ui_url;
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Add an Integration",
|
||||
description:
|
||||
<div>
|
||||
You are about to taken to a third-party site so you can authenticate your account for use with {integrationsUrl}.<br/>
|
||||
Do you wish to continue?
|
||||
</div>,
|
||||
button: "Continue",
|
||||
onFinished: function(confirmed) {
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
let width = window.screen.width > 1024 ? 1024 : window.screen.width;
|
||||
let height = window.screen.height > 800 ? 800 : window.screen.height;
|
||||
let left = (window.screen.width - width) / 2;
|
||||
let top = (window.screen.height - height) / 2;
|
||||
window.open(completeUrl, '_blank', `height=${height}, width=${width}, top=${top}, left=${left},`);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
var mxEvent = this.props.mxEvent;
|
||||
var content = mxEvent.getContent();
|
||||
|
||||
var body = HtmlUtils.bodyToHtml(content, this.props.highlights, {});
|
||||
|
||||
if (this.props.highlightLink) {
|
||||
body = <a href={ this.props.highlightLink }>{ body }</a>;
|
||||
}
|
||||
else if (content.data && typeof content.data["org.matrix.neb.starter_link"] === "string") {
|
||||
body = <a href="#" onClick={ this.onStarterLinkClick.bind(this, content.data["org.matrix.neb.starter_link"]) }>{ body }</a>;
|
||||
}
|
||||
|
||||
var widgets;
|
||||
if (this.state.links.length && !this.state.widgetHidden && this.props.showUrlPreview) {
|
||||
|
|
|
@ -57,12 +57,14 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onConferenceNotificationClick: function() {
|
||||
onConferenceNotificationClick: function(ev, type) {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "video",
|
||||
type: type,
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -85,14 +87,20 @@ module.exports = React.createClass({
|
|||
|
||||
var conferenceCallNotification = null;
|
||||
if (this.props.displayConfCallNotification) {
|
||||
var supportedText;
|
||||
var supportedText, joinText;
|
||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||
supportedText = " (unsupported)";
|
||||
}
|
||||
else {
|
||||
joinText = (<span>
|
||||
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice')}} href="#">voice</a>
|
||||
or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video') }} href="#">video</a>.
|
||||
</span>);
|
||||
|
||||
}
|
||||
conferenceCallNotification = (
|
||||
<div className="mx_RoomView_ongoingConfCallNotification"
|
||||
onClick={this.onConferenceNotificationClick}>
|
||||
Ongoing conference call {supportedText}
|
||||
<div className="mx_RoomView_ongoingConfCallNotification">
|
||||
Ongoing conference call{ supportedText }. { joinText }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,43 +14,45 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
import React from 'react';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberDeviceInfo',
|
||||
propTypes: {
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
device: React.PropTypes.object.isRequired,
|
||||
},
|
||||
export default class MemberDeviceInfo extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onVerifyClick = this.onVerifyClick.bind(this);
|
||||
this.onUnverifyClick = this.onUnverifyClick.bind(this);
|
||||
this.onBlockClick = this.onBlockClick.bind(this);
|
||||
this.onUnblockClick = this.onUnblockClick.bind(this);
|
||||
}
|
||||
|
||||
onVerifyClick: function() {
|
||||
onVerifyClick() {
|
||||
MatrixClientPeg.get().setDeviceVerified(
|
||||
this.props.userId, this.props.device.id, true
|
||||
this.props.userId, this.props.device.deviceId, true
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
onUnverifyClick: function() {
|
||||
onUnverifyClick() {
|
||||
MatrixClientPeg.get().setDeviceVerified(
|
||||
this.props.userId, this.props.device.id, false
|
||||
this.props.userId, this.props.device.deviceId, false
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
onBlockClick: function() {
|
||||
onBlockClick() {
|
||||
MatrixClientPeg.get().setDeviceBlocked(
|
||||
this.props.userId, this.props.device.id, true
|
||||
this.props.userId, this.props.device.deviceId, true
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
onUnblockClick: function() {
|
||||
onUnblockClick() {
|
||||
MatrixClientPeg.get().setDeviceBlocked(
|
||||
this.props.userId, this.props.device.id, false
|
||||
this.props.userId, this.props.device.deviceId, false
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
var indicator = null, blockButton = null, verifyButton = null;
|
||||
if (this.props.device.blocked) {
|
||||
if (this.props.device.isBlocked()) {
|
||||
blockButton = (
|
||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblock"
|
||||
onClick={this.onUnblockClick}>
|
||||
|
@ -66,7 +68,7 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.device.verified) {
|
||||
if (this.props.device.isVerified()) {
|
||||
verifyButton = (
|
||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||
onClick={this.onUnverifyClick}>
|
||||
|
@ -82,22 +84,22 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.device.blocked) {
|
||||
if (this.props.device.isBlocked()) {
|
||||
indicator = (
|
||||
<div className="mx_MemberDeviceInfo_blocked">✖</div>
|
||||
<div className="mx_MemberDeviceInfo_blocked">Blocked</div>
|
||||
);
|
||||
} else if (this.props.device.verified) {
|
||||
} else if (this.props.device.isVerified()) {
|
||||
indicator = (
|
||||
<div className="mx_MemberDeviceInfo_verified">✔</div>
|
||||
<div className="mx_MemberDeviceInfo_verified">Verified</div>
|
||||
);
|
||||
|
||||
} else {
|
||||
indicator = (
|
||||
<div className="mx_MemberDeviceInfo_unverified">?</div>
|
||||
<div className="mx_MemberDeviceInfo_unverified">Unverified</div>
|
||||
);
|
||||
}
|
||||
|
||||
var deviceName = this.props.device.display_name || this.props.device.id;
|
||||
var deviceName = this.props.device.display_name || this.props.device.deviceId;
|
||||
|
||||
return (
|
||||
<div className="mx_MemberDeviceInfo">
|
||||
|
@ -107,5 +109,11 @@ module.exports = React.createClass({
|
|||
{blockButton}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MemberDeviceInfo.displayName = 'MemberDeviceInfo';
|
||||
MemberDeviceInfo.propTypes = {
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
device: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
@ -159,7 +159,7 @@ module.exports = React.createClass({
|
|||
if (userId == this.props.member.userId) {
|
||||
// no need to re-download the whole thing; just update our copy of
|
||||
// the list.
|
||||
var devices = MatrixClientPeg.get().listDeviceKeys(userId);
|
||||
var devices = MatrixClientPeg.get().getStoredDevicesForUser(userId);
|
||||
this.setState({devices: devices});
|
||||
}
|
||||
},
|
||||
|
@ -195,7 +195,7 @@ module.exports = React.createClass({
|
|||
// we got cancelled - presumably a different user now
|
||||
return;
|
||||
}
|
||||
var devices = client.listDeviceKeys(member.userId);
|
||||
var devices = client.getStoredDevicesForUser(member.userId);
|
||||
self.setState({devicesLoading: false, devices: devices});
|
||||
}, function(err) {
|
||||
console.log("Error downloading devices", err);
|
||||
|
|
|
@ -65,7 +65,6 @@ module.exports = React.createClass({
|
|||
// Default to false if it's undefined, otherwise react complains about changing
|
||||
// components from uncontrolled to controlled
|
||||
isRoomPublished: this._originalIsRoomPublished || false,
|
||||
scalar_token: null,
|
||||
scalar_error: null,
|
||||
};
|
||||
},
|
||||
|
@ -81,11 +80,16 @@ module.exports = React.createClass({
|
|||
console.error("Failed to get room visibility: " + err);
|
||||
});
|
||||
|
||||
this.getScalarToken().done((token) => {
|
||||
this.setState({scalar_token: token});
|
||||
}, (err) => {
|
||||
this.setState({scalar_error: err});
|
||||
});
|
||||
if (UserSettingsStore.isFeatureEnabled("integration_management")) {
|
||||
this.scalarClient = new ScalarAuthClient();
|
||||
this.scalarClient.connect().done(() => {
|
||||
this.forceUpdate();
|
||||
}, (err) => {
|
||||
this.setState({
|
||||
scalar_error: err
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: 'ui_opacity',
|
||||
|
@ -249,7 +253,7 @@ module.exports = React.createClass({
|
|||
var roomId = this.props.room.roomId;
|
||||
return MatrixClientPeg.get().sendStateEvent(
|
||||
roomId, "m.room.encryption",
|
||||
{ algorithm: "m.olm.v1.curve25519-aes-sha2" }
|
||||
{ algorithm: "m.megolm.v1.aes-sha2" }
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -395,34 +399,13 @@ module.exports = React.createClass({
|
|||
roomState.mayClientSendStateEvent("m.room.guest_access", cli))
|
||||
},
|
||||
|
||||
getScalarInterfaceUrl: function() {
|
||||
var url = SdkConfig.get().integrations_ui_url;
|
||||
url += "?scalar_token=" + encodeURIComponent(this.state.scalar_token);
|
||||
url += "&room_id=" + encodeURIComponent(this.props.room.roomId);
|
||||
return url;
|
||||
},
|
||||
|
||||
getScalarToken() {
|
||||
var tok = window.localStorage.getItem("mx_scalar_token");
|
||||
if (tok) return q(tok);
|
||||
|
||||
// No saved token, so do the dance to get one. First, we
|
||||
// need an openid bearer token from the HS.
|
||||
return MatrixClientPeg.get().getOpenIdToken().then((token_object) => {
|
||||
// Now we can send that to scalar and exchange it for a scalar token
|
||||
var scalar_auth_client = new ScalarAuthClient();
|
||||
return scalar_auth_client.getScalarToken(token_object);
|
||||
}).then((token_object) => {
|
||||
window.localStorage.setItem("mx_scalar_token", token_object);
|
||||
return token_object;
|
||||
});
|
||||
},
|
||||
|
||||
onManageIntegrations(ev) {
|
||||
ev.preventDefault();
|
||||
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||
Modal.createDialog(IntegrationsManager, {
|
||||
src: this.state.scalar_token ? this.getScalarInterfaceUrl() : null
|
||||
src: this.scalarClient.hasCredentials() ?
|
||||
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
|
||||
null
|
||||
}, "");
|
||||
},
|
||||
|
||||
|
@ -649,7 +632,7 @@ module.exports = React.createClass({
|
|||
if (UserSettingsStore.isFeatureEnabled("integration_management")) {
|
||||
let integrations_body;
|
||||
|
||||
if (this.state.scalar_token) {
|
||||
if (this.scalarClient.hasCredentials()) {
|
||||
integrations_body = (
|
||||
<div className="mx_RoomSettings_settings">
|
||||
<a href="#" onClick={ this.onManageIntegrations }>Manage integrations</a>
|
||||
|
|
|
@ -95,8 +95,10 @@ module.exports = React.createClass({
|
|||
if (call) {
|
||||
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
|
||||
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
|
||||
// give a separate element for audio stream playback - both for voice calls
|
||||
// and for the voice stream of screen captures
|
||||
// always use a separate element for audio stream playback.
|
||||
// this is to let us move CallView around the DOM without interrupting remote audio
|
||||
// during playback, by having the audio rendered by a top-level <audio/> element.
|
||||
// rather than being rendered by the main remoteVideo <video/> element.
|
||||
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
|
||||
}
|
||||
if (call && call.type === "video" && call.call_state !== "ended" && call.call_state !== "ringing") {
|
||||
|
|
|
@ -50,7 +50,14 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getRemoteAudioElement: function() {
|
||||
return this.refs.remoteAudio;
|
||||
// this needs to be somewhere at the top of the DOM which
|
||||
// always exists to avoid audio interruptions.
|
||||
// Might as well just use DOM.
|
||||
var remoteAudioElement = document.getElementById("remoteAudio");
|
||||
if (!remoteAudioElement) {
|
||||
console.error("Failed to find remoteAudio element - cannot play audio! You need to add an <audio/> to the DOM.");
|
||||
}
|
||||
return remoteAudioElement;
|
||||
},
|
||||
|
||||
getLocalVideoElement: function() {
|
||||
|
@ -106,7 +113,6 @@ module.exports = React.createClass({
|
|||
<div className="mx_VideoView_remoteVideoFeed">
|
||||
<VideoFeed ref="remote" onResize={this.props.onResize}
|
||||
maxHeight={maxVideoHeight} />
|
||||
<audio ref="remoteAudio"/>
|
||||
</div>
|
||||
<div className="mx_VideoView_localVideoFeed">
|
||||
<VideoFeed ref="local"/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue