mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into matthew/warn-unknown-devices
commit
5538ce7c30
93
src/Modal.js
93
src/Modal.js
|
@ -21,6 +21,8 @@ var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
|
||||||
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap an asynchronous loader function with a react component which shows a
|
* Wrap an asynchronous loader function with a react component which shows a
|
||||||
* spinner until the real component loads.
|
* spinner until the real component loads.
|
||||||
|
@ -67,26 +69,37 @@ const AsyncWrapper = React.createClass({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let _counter = 0;
|
class ModalManager {
|
||||||
|
constructor() {
|
||||||
|
this._counter = 0;
|
||||||
|
|
||||||
module.exports = {
|
/** list of the modals we have stacked up, with the most recent at [0] */
|
||||||
DialogContainerId: "mx_Dialog_Container",
|
this._modals = [
|
||||||
|
/* {
|
||||||
|
elem: React component for this dialog
|
||||||
|
onFinished: caller-supplied onFinished callback
|
||||||
|
className: CSS class for the dialog wrapper div
|
||||||
|
} */
|
||||||
|
];
|
||||||
|
|
||||||
getOrCreateContainer: function() {
|
this.closeAll = this.closeAll.bind(this);
|
||||||
var container = document.getElementById(this.DialogContainerId);
|
}
|
||||||
|
|
||||||
|
getOrCreateContainer() {
|
||||||
|
let container = document.getElementById(DIALOG_CONTAINER_ID);
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
container = document.createElement("div");
|
container = document.createElement("div");
|
||||||
container.id = this.DialogContainerId;
|
container.id = DIALOG_CONTAINER_ID;
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
},
|
}
|
||||||
|
|
||||||
createDialog: function(Element, props, className) {
|
createDialog(Element, props, className) {
|
||||||
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a modal view.
|
* Open a modal view.
|
||||||
|
@ -107,31 +120,73 @@ module.exports = {
|
||||||
*
|
*
|
||||||
* @param {String} className CSS class to apply to the modal wrapper
|
* @param {String} className CSS class to apply to the modal wrapper
|
||||||
*/
|
*/
|
||||||
createDialogAsync: function(loader, props, className) {
|
createDialogAsync(loader, props, className) {
|
||||||
var self = this;
|
var self = this;
|
||||||
// never call this via modal.close() from onFinished() otherwise it will loop
|
const modal = {};
|
||||||
|
|
||||||
|
// never call this from onFinished() otherwise it will loop
|
||||||
|
//
|
||||||
|
// nb explicit function() rather than arrow function, to get `arguments`
|
||||||
var closeDialog = function() {
|
var closeDialog = function() {
|
||||||
if (props && props.onFinished) props.onFinished.apply(null, arguments);
|
if (props && props.onFinished) props.onFinished.apply(null, arguments);
|
||||||
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
|
var i = self._modals.indexOf(modal);
|
||||||
|
if (i >= 0) {
|
||||||
|
self._modals.splice(i, 1);
|
||||||
|
}
|
||||||
|
self._reRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
||||||
// otherwise we'll get confused.
|
// otherwise we'll get confused.
|
||||||
const modalCount = _counter++;
|
const modalCount = this._counter++;
|
||||||
|
|
||||||
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
||||||
// property set here so you can't close the dialog from a button click!
|
// property set here so you can't close the dialog from a button click!
|
||||||
|
modal.elem = (
|
||||||
|
<AsyncWrapper key={modalCount} loader={loader} {...props}
|
||||||
|
onFinished={closeDialog}/>
|
||||||
|
);
|
||||||
|
modal.onFinished = props ? props.onFinished : null;
|
||||||
|
modal.className = className;
|
||||||
|
|
||||||
|
this._modals.unshift(modal);
|
||||||
|
|
||||||
|
this._reRender();
|
||||||
|
return {close: closeDialog};
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAll() {
|
||||||
|
const modals = this._modals;
|
||||||
|
this._modals = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < modals.length; i++) {
|
||||||
|
const m = modals[i];
|
||||||
|
if (m.onFinished) {
|
||||||
|
m.onFinished(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._reRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
_reRender() {
|
||||||
|
if (this._modals.length == 0) {
|
||||||
|
ReactDOM.unmountComponentAtNode(this.getOrCreateContainer());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modal = this._modals[0];
|
||||||
var dialog = (
|
var dialog = (
|
||||||
<div className={"mx_Dialog_wrapper " + className}>
|
<div className={"mx_Dialog_wrapper " + modal.className}>
|
||||||
<div className="mx_Dialog">
|
<div className="mx_Dialog">
|
||||||
<AsyncWrapper key={modalCount} loader={loader} {...props} onFinished={closeDialog}/>
|
{modal.elem}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_background" onClick={ closeDialog.bind(this, false) }></div>
|
<div className="mx_Dialog_background" onClick={ this.closeAll }></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactDOM.render(dialog, this.getOrCreateContainer());
|
ReactDOM.render(dialog, this.getOrCreateContainer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {close: closeDialog};
|
export default new ModalManager();
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ var DEFAULTS = {
|
||||||
integrations_ui_url: "https://scalar.vector.im/",
|
integrations_ui_url: "https://scalar.vector.im/",
|
||||||
// Base URL to the REST interface of the integrations server
|
// Base URL to the REST interface of the integrations server
|
||||||
integrations_rest_url: "https://scalar.vector.im/api",
|
integrations_rest_url: "https://scalar.vector.im/api",
|
||||||
|
// Where to send bug reports. If not specified, bugs cannot be sent.
|
||||||
|
bug_report_endpoint_url: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SdkConfig {
|
class SdkConfig {
|
||||||
|
|
|
@ -26,6 +26,7 @@ var UserSettingsStore = require('../../UserSettingsStore');
|
||||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
var Email = require('../../email');
|
var Email = require('../../email');
|
||||||
var AddThreepid = require('../../AddThreepid');
|
var AddThreepid = require('../../AddThreepid');
|
||||||
|
var SdkConfig = require('../../SdkConfig');
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
|
|
||||||
// if this looks like a release, use the 'version' from package.json; else use
|
// if this looks like a release, use the 'version' from package.json; else use
|
||||||
|
@ -388,6 +389,14 @@ module.exports = React.createClass({
|
||||||
Modal.createDialog(DeactivateAccountDialog, {});
|
Modal.createDialog(DeactivateAccountDialog, {});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onBugReportClicked: function() {
|
||||||
|
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
||||||
|
if (!BugReportDialog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Modal.createDialog(BugReportDialog, {});
|
||||||
|
},
|
||||||
|
|
||||||
_onInviteStateChange: function(event, member, oldMembership) {
|
_onInviteStateChange: function(event, member, oldMembership) {
|
||||||
if (member.userId === this._me && oldMembership === "invite") {
|
if (member.userId === this._me && oldMembership === "invite") {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
@ -547,6 +556,23 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderBugReport: function() {
|
||||||
|
if (!SdkConfig.get().bug_report_endpoint_url) {
|
||||||
|
return <div />
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Bug Report</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
<p>Found a bug?</p>
|
||||||
|
<button className="mx_UserSettings_button danger"
|
||||||
|
onClick={this._onBugReportClicked}>Report it
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
_renderLabs: function() {
|
_renderLabs: function() {
|
||||||
// default to enabled if undefined
|
// default to enabled if undefined
|
||||||
if (this.props.enableLabs === false) return null;
|
if (this.props.enableLabs === false) return null;
|
||||||
|
@ -800,6 +826,7 @@ module.exports = React.createClass({
|
||||||
{this._renderDevicesPanel()}
|
{this._renderDevicesPanel()}
|
||||||
{this._renderCryptoInfo()}
|
{this._renderCryptoInfo()}
|
||||||
{this._renderBulkOptions()}
|
{this._renderBulkOptions()}
|
||||||
|
{this._renderBugReport()}
|
||||||
|
|
||||||
<h3>Advanced</h3>
|
<h3>Advanced</h3>
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ var linkify = require('linkifyjs');
|
||||||
var linkifyElement = require('linkifyjs/element');
|
var linkifyElement = require('linkifyjs/element');
|
||||||
var linkifyMatrix = require('../../../linkify-matrix');
|
var linkifyMatrix = require('../../../linkify-matrix');
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import {CancelButton} from './SimpleRoomHeader';
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -184,7 +185,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
save_button = <AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</AccessibleButton>;
|
save_button = <AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</AccessibleButton>;
|
||||||
cancel_button = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>;
|
cancel_button = <CancelButton onClick={this.props.onCancelClick}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.saving) {
|
if (this.props.saving) {
|
||||||
|
|
|
@ -16,16 +16,27 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var sdk = require('../../../index');
|
import dis from '../../../dispatcher';
|
||||||
var dis = require("../../../dispatcher");
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
|
// cancel button which is shared between room header and simple room header
|
||||||
|
export function CancelButton(props) {
|
||||||
|
const {onClick} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibleButton className='mx_RoomHeader_cancelButton' onClick={onClick}>
|
||||||
|
<img src="img/cancel.svg" className='mx_filterFlipColor'
|
||||||
|
width="18" height="18" alt="Cancel"/>
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A stripped-down room header used for things like the user settings
|
* A stripped-down room header used for things like the user settings
|
||||||
* and room directory.
|
* and room directory.
|
||||||
*/
|
*/
|
||||||
module.exports = React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'SimpleRoomHeader',
|
displayName: 'SimpleRoomHeader',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -41,15 +52,15 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
let cancelButton;
|
||||||
|
|
||||||
var cancelButton;
|
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
cancelButton = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>;
|
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
var showRhsButton;
|
let showRhsButton;
|
||||||
/* // don't bother cluttering things up with this for now.
|
/* // don't bother cluttering things up with this for now.
|
||||||
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
if (this.props.collapsedRhs) {
|
if (this.props.collapsedRhs) {
|
||||||
showRhsButton =
|
showRhsButton =
|
||||||
<div className="mx_RoomHeader_button" style={{ float: 'right' }} onClick={this.onShowRhsClick} title=">">
|
<div className="mx_RoomHeader_button" style={{ float: 'right' }} onClick={this.onShowRhsClick} title=">">
|
||||||
|
|
Loading…
Reference in New Issue