diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js
new file mode 100644
index 0000000000..b7b81688e1
--- /dev/null
+++ b/src/AsyncWrapper.js
@@ -0,0 +1,92 @@
+/*
+Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+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 createReactClass from 'create-react-class';
+import * as sdk from './index';
+import PropTypes from 'prop-types';
+import { _t } from './languageHandler';
+
+/**
+ * Wrap an asynchronous loader function with a react component which shows a
+ * spinner until the real component loads.
+ */
+export default createReactClass({
+ propTypes: {
+ /** A promise which resolves with the real component
+ */
+ prom: PropTypes.object.isRequired,
+ },
+
+ getInitialState: function() {
+ return {
+ component: null,
+ error: null,
+ };
+ },
+
+ componentWillMount: function() {
+ this._unmounted = false;
+ // XXX: temporary logging to try to diagnose
+ // https://github.com/vector-im/riot-web/issues/3148
+ console.log('Starting load of AsyncWrapper for modal');
+ this.props.prom.then((result) => {
+ if (this._unmounted) {
+ return;
+ }
+ // 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) => {
+ console.warn('AsyncWrapper promise failed', e);
+ this.setState({error: e});
+ });
+ },
+
+ componentWillUnmount: function() {
+ this._unmounted = true;
+ },
+
+ _onWrapperCancelClick: function() {
+ this.props.onFinished(false);
+ },
+
+ render: function() {
+ if (this.state.component) {
+ const Component = this.state.component;
+ return ;
+ } else if (this.state.error) {
+ const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ return
+ {_t("Unable to load! Check your network connectivity and try again.")}
+
+ ;
+ } else {
+ // show a spinner until the component is loaded.
+ const Spinner = sdk.getComponent("elements.Spinner");
+ return ;
+ }
+ },
+});
+
diff --git a/src/Modal.js b/src/Modal.js
index 29d3af2e74..b6215b2b5a 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -17,87 +17,14 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
import Analytics from './Analytics';
-import * as sdk from './index';
import dis from './dispatcher';
-import { _t } from './languageHandler';
-import {defer} from "./utils/promise";
+import {defer} from './utils/promise';
+import AsyncWrapper from './AsyncWrapper';
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
-/**
- * Wrap an asynchronous loader function with a react component which shows a
- * spinner until the real component loads.
- */
-const AsyncWrapper = createReactClass({
- propTypes: {
- /** A promise which resolves with the real component
- */
- prom: PropTypes.object.isRequired,
- },
-
- getInitialState: function() {
- return {
- component: null,
- error: null,
- };
- },
-
- componentWillMount: function() {
- this._unmounted = false;
- // XXX: temporary logging to try to diagnose
- // https://github.com/vector-im/riot-web/issues/3148
- console.log('Starting load of AsyncWrapper for modal');
- this.props.prom.then((result) => {
- if (this._unmounted) {
- return;
- }
- // 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) => {
- console.warn('AsyncWrapper promise failed', e);
- this.setState({error: e});
- });
- },
-
- componentWillUnmount: function() {
- this._unmounted = true;
- },
-
- _onWrapperCancelClick: function() {
- this.props.onFinished(false);
- },
-
- render: function() {
- if (this.state.component) {
- const Component = this.state.component;
- return ;
- } else if (this.state.error) {
- const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
- const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
- return
- {_t("Unable to load! Check your network connectivity and try again.")}
-
- ;
- } else {
- // show a spinner until the component is loaded.
- const Spinner = sdk.getComponent("elements.Spinner");
- return ;
- }
- },
-});
-
class ModalManager {
constructor() {
this._counter = 0;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 1d52322bc2..af77be7ea4 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -21,6 +21,9 @@
"Analytics": "Analytics",
"The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.",
+ "Error": "Error",
+ "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
+ "Dismiss": "Dismiss",
"Call Failed": "Call Failed",
"There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.",
"Review Devices": "Review Devices",
@@ -105,9 +108,6 @@
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.",
"Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.",
"Trust": "Trust",
- "Error": "Error",
- "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 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",