Merge pull request #649 from matrix-org/rav/stack_dialogs

Allow modals to stack up
This commit is contained in:
David Baker 2017-01-25 16:45:50 +00:00 committed by GitHub
commit 8a5e19f64f

View file

@ -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();
},
};