Update async dialog interface to use promises
Hopefully makes the syntax a bit nicer. Also uses ES6 async import rather than require.ensure which is now deprecated. Also also displays an error if the component fails to load rather than falling over in a heap, which is nice.
This commit is contained in:
parent
93cd3b4206
commit
985966f8be
8 changed files with 66 additions and 47 deletions
2
.babelrc
2
.babelrc
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"presets": ["react", "es2015", "es2016"],
|
"presets": ["react", "es2015", "es2016"],
|
||||||
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports"]
|
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports", "syntax-dynamic-import"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
"test-multi": "karma start"
|
"test-multi": "karma start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"blueimp-canvas-to-blob": "^3.5.0",
|
"blueimp-canvas-to-blob": "^3.5.0",
|
||||||
|
|
46
src/Modal.js
46
src/Modal.js
|
@ -23,6 +23,7 @@ import PropTypes from 'prop-types';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
|
||||||
|
@ -32,15 +33,15 @@ const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
*/
|
*/
|
||||||
const AsyncWrapper = React.createClass({
|
const AsyncWrapper = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
/** A function which takes a 'callback' argument which it will call
|
/** A promise which resolves with the real component
|
||||||
* with the real component once it loads.
|
|
||||||
*/
|
*/
|
||||||
loader: PropTypes.func.isRequired,
|
prom: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
component: null,
|
component: null,
|
||||||
|
error: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -49,14 +50,20 @@ const AsyncWrapper = React.createClass({
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
// https://github.com/vector-im/riot-web/issues/3148
|
||||||
console.log('Starting load of AsyncWrapper for modal');
|
console.log('Starting load of AsyncWrapper for modal');
|
||||||
this.props.loader((e) => {
|
this.props.prom.then((result) => {
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
// https://github.com/vector-im/riot-web/issues/3148
|
||||||
console.log('AsyncWrapper load completed with '+e.displayName);
|
console.log('AsyncWrapper load completed with '+result.displayName);
|
||||||
if (this._unmounted) {
|
if (this._unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({component: e});
|
// Take the 'default' member if it's there, then we support
|
||||||
|
// passing in just an import()ed module, since ES6 async import
|
||||||
|
// always returns a module *namespace*.
|
||||||
|
const component = result.default ? result.default : result;
|
||||||
|
this.setState({component});
|
||||||
|
}).catch((e) => {
|
||||||
|
this.setState({error: e});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -64,11 +71,27 @@ const AsyncWrapper = React.createClass({
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onWrapperCancelClick: function() {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const {loader, ...otherProps} = this.props;
|
const {loader, ...otherProps} = this.props;
|
||||||
if (this.state.component) {
|
if (this.state.component) {
|
||||||
const Component = this.state.component;
|
const Component = this.state.component;
|
||||||
return <Component {...otherProps} />;
|
return <Component {...otherProps} />;
|
||||||
|
} else if (this.state.error) {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <BaseDialog onFinished={this.props.onFinished}
|
||||||
|
title={_t("Error")}
|
||||||
|
>
|
||||||
|
{_t("Unable to load! Check your network connectivity and try again.")}
|
||||||
|
<DialogButtons primaryButton={_t("Dismiss")}
|
||||||
|
onPrimaryButtonClick={this._onWrapperCancelClick}
|
||||||
|
hasCancel={false}
|
||||||
|
/>
|
||||||
|
</BaseDialog>;
|
||||||
} else {
|
} else {
|
||||||
// show a spinner until the component is loaded.
|
// show a spinner until the component is loaded.
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -115,7 +138,7 @@ class ModalManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
createDialog(Element, ...rest) {
|
createDialog(Element, ...rest) {
|
||||||
return this.createDialogAsync((cb) => {cb(Element);}, ...rest);
|
return this.createDialogAsync(new Promise(resolve => resolve(Element)), ...rest);
|
||||||
}
|
}
|
||||||
|
|
||||||
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
|
createTrackedDialogAsync(analyticsAction, analyticsInfo, ...rest) {
|
||||||
|
@ -133,9 +156,8 @@ class ModalManager {
|
||||||
* require(['<module>'], cb);
|
* require(['<module>'], cb);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param {Function} loader a function which takes a 'callback' argument,
|
* @param {Promise} prom a promise which resolves with a React component
|
||||||
* which it should call with a React component which will be displayed as
|
* which will be displayed as the modal view.
|
||||||
* the modal view.
|
|
||||||
*
|
*
|
||||||
* @param {Object} props properties to pass to the displayed
|
* @param {Object} props properties to pass to the displayed
|
||||||
* component. (We will also pass an 'onFinished' property.)
|
* component. (We will also pass an 'onFinished' property.)
|
||||||
|
@ -147,7 +169,7 @@ class ModalManager {
|
||||||
* Also, when closed, all modals will be removed
|
* Also, when closed, all modals will be removed
|
||||||
* from the stack.
|
* from the stack.
|
||||||
*/
|
*/
|
||||||
createDialogAsync(loader, props, className, isPriorityModal) {
|
createDialogAsync(prom, props, className, isPriorityModal) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const modal = {};
|
const modal = {};
|
||||||
|
|
||||||
|
@ -178,7 +200,7 @@ class ModalManager {
|
||||||
// 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 = (
|
modal.elem = (
|
||||||
<AsyncWrapper key={modalCount} loader={loader} {...props}
|
<AsyncWrapper key={modalCount} prom={prom} {...props}
|
||||||
onFinished={closeDialog} />
|
onFinished={closeDialog} />
|
||||||
);
|
);
|
||||||
modal.onFinished = props ? props.onFinished : null;
|
modal.onFinished = props ? props.onFinished : null;
|
||||||
|
|
|
@ -589,23 +589,21 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onExportE2eKeysClicked: function() {
|
_onExportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '', (cb) => {
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
require.ensure(['../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
import('../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||||
cb(require('../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}, {
|
},
|
||||||
matrixClient: MatrixClientPeg.get(),
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onImportE2eKeysClicked: function() {
|
_onImportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Import E2E Keys', '', (cb) => {
|
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
||||||
require.ensure(['../../async-components/views/dialogs/ImportE2eKeysDialog'], () => {
|
import('../../async-components/views/dialogs/ImportE2eKeysDialog'),
|
||||||
cb(require('../../async-components/views/dialogs/ImportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}, {
|
},
|
||||||
matrixClient: MatrixClientPeg.get(),
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderGroupSettings: function() {
|
_renderGroupSettings: function() {
|
||||||
|
|
|
@ -121,13 +121,12 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onExportE2eKeysClicked: function() {
|
_onExportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password', (cb) => {
|
Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password',
|
||||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}, {
|
},
|
||||||
matrixClient: MatrixClientPeg.get(),
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputChanged: function(stateKey, ev) {
|
onInputChanged: function(stateKey, ev) {
|
||||||
|
|
|
@ -416,11 +416,10 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
onCryptoClicked: function(e) {
|
onCryptoClicked: function(e) {
|
||||||
const event = this.props.mxEvent;
|
const event = this.props.mxEvent;
|
||||||
|
|
||||||
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', (cb) => {
|
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
|
||||||
require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb);
|
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
|
||||||
}, {
|
{event},
|
||||||
event: event,
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRequestKeysClick: function() {
|
onRequestKeysClick: function() {
|
||||||
|
|
|
@ -179,13 +179,12 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onExportE2eKeysClicked: function() {
|
_onExportE2eKeysClicked: function() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', (cb) => {
|
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
|
||||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
{
|
||||||
}, "e2e-export");
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}, {
|
},
|
||||||
matrixClient: MatrixClientPeg.get(),
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onClickChange: function(ev) {
|
onClickChange: function(ev) {
|
||||||
|
|
|
@ -82,6 +82,8 @@
|
||||||
"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:",
|
||||||
|
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
|
||||||
|
"Dismiss": "Dismiss",
|
||||||
"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",
|
||||||
|
@ -644,7 +646,6 @@
|
||||||
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.",
|
"You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.": "You can use the custom server options to sign into other Matrix servers by specifying a different Home server URL.",
|
||||||
"This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.",
|
"This allows you to use this app with an existing Matrix account on a different home server.": "This allows you to use this app with an existing Matrix account on a different home server.",
|
||||||
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.",
|
"You can also set a custom identity server but this will typically prevent interaction with users based on email address.": "You can also set a custom identity server but this will typically prevent interaction with users based on email address.",
|
||||||
"Dismiss": "Dismiss",
|
|
||||||
"To continue, please enter your password.": "To continue, please enter your password.",
|
"To continue, please enter your password.": "To continue, please enter your password.",
|
||||||
"Password:": "Password:",
|
"Password:": "Password:",
|
||||||
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
|
"Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies",
|
||||||
|
|
Loading…
Reference in a new issue