Extract AlertManagerState from AlertManager (#2163)

This commit is contained in:
Alexander Skvortsov 2020-06-30 18:06:59 -04:00 committed by GitHub
parent 417e878c0c
commit 95f367c837
12 changed files with 107 additions and 103 deletions

View File

@ -2,7 +2,6 @@ import Page from '../../common/components/Page';
import FieldSet from '../../common/components/FieldSet'; import FieldSet from '../../common/components/FieldSet';
import Select from '../../common/components/Select'; import Select from '../../common/components/Select';
import Button from '../../common/components/Button'; import Button from '../../common/components/Button';
import Alert from '../../common/components/Alert';
import saveSettings from '../utils/saveSettings'; import saveSettings from '../utils/saveSettings';
import ItemList from '../../common/utils/ItemList'; import ItemList from '../../common/utils/ItemList';
import Switch from '../../common/components/Switch'; import Switch from '../../common/components/Switch';
@ -186,7 +185,10 @@ export default class BasicsPage extends Page {
saveSettings(settings) saveSettings(settings)
.then(() => { .then(() => {
app.alerts.show((this.successAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.basics.saved_message') }))); this.successAlert = app.alerts.show({
type: 'success',
children: app.translator.trans('core.admin.basics.saved_message'),
});
}) })
.catch(() => {}) .catch(() => {})
.then(() => { .then(() => {

View File

@ -179,9 +179,10 @@ export default class MailPage extends Page {
}) })
.then((response) => { .then((response) => {
this.sendingTest = false; this.sendingTest = false;
app.alerts.show( this.testEmailSuccessAlert = app.alerts.show({
(this.testEmailSuccessAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.email.send_test_mail_success') })) type: 'success',
); children: app.translator.trans('core.admin.email.send_test_mail_success'),
});
}) })
.catch((error) => { .catch((error) => {
this.sendingTest = false; this.sendingTest = false;
@ -204,7 +205,10 @@ export default class MailPage extends Page {
saveSettings(settings) saveSettings(settings)
.then(() => { .then(() => {
app.alerts.show((this.successAlert = new Alert({ type: 'success', children: app.translator.trans('core.admin.basics.saved_message') }))); this.successAlert = app.alerts.show({
type: 'success',
children: app.translator.trans('core.admin.basics.saved_message'),
});
}) })
.catch(() => {}) .catch(() => {})
.then(() => { .then(() => {

View File

@ -1,5 +1,4 @@
import ItemList from './utils/ItemList'; import ItemList from './utils/ItemList';
import Alert from './components/Alert';
import Button from './components/Button'; import Button from './components/Button';
import ModalManager from './components/ModalManager'; import ModalManager from './components/ModalManager';
import AlertManager from './components/AlertManager'; import AlertManager from './components/AlertManager';
@ -23,6 +22,7 @@ import Group from './models/Group';
import Notification from './models/Notification'; import Notification from './models/Notification';
import { flattenDeep } from 'lodash-es'; import { flattenDeep } from 'lodash-es';
import PageState from './states/PageState'; import PageState from './states/PageState';
import AlertManagerState from './states/AlertManagerState';
/** /**
* The `App` class provides a container for an application, as well as various * The `App` class provides a container for an application, as well as various
@ -109,13 +109,13 @@ export default class Application {
booted = false; booted = false;
/** /**
* An Alert that was shown as a result of an AJAX request error. If present, * The key for an Alert that was shown as a result of an AJAX request error.
* it will be dismissed on the next successful request. * If present, it will be dismissed on the next successful request.
* *
* @type {null|Alert} * @type {int}
* @private * @private
*/ */
requestError = null; requestErrorAlert = null;
/** /**
* The page the app is currently on. * The page the app is currently on.
@ -139,6 +139,11 @@ export default class Application {
*/ */
previous = new PageState(null); previous = new PageState(null);
/*
* An object that manages the state of active alerts.
*/
alerts = new AlertManagerState();
data; data;
title = ''; title = '';
@ -175,7 +180,7 @@ export default class Application {
mount(basePath = '') { mount(basePath = '') {
this.modal = m.mount(document.getElementById('modal'), <ModalManager />); this.modal = m.mount(document.getElementById('modal'), <ModalManager />);
this.alerts = m.mount(document.getElementById('alerts'), <AlertManager />); m.mount(document.getElementById('alerts'), <AlertManager state={this.alerts} />);
this.drawer = new Drawer(); this.drawer = new Drawer();
@ -313,7 +318,7 @@ export default class Application {
} }
}; };
if (this.requestError) this.alerts.dismiss(this.requestError.alert); if (this.requestErrorAlert) this.alerts.dismiss(this.requestErrorAlert);
// Now make the request. If it's a failure, inspect the error that was // Now make the request. If it's a failure, inspect the error that was
// returned and show an alert containing its contents. // returned and show an alert containing its contents.
@ -322,8 +327,6 @@ export default class Application {
m.request(options).then( m.request(options).then(
(response) => deferred.resolve(response), (response) => deferred.resolve(response),
(error) => { (error) => {
this.requestError = error;
let children; let children;
switch (error.status) { switch (error.status) {
@ -357,7 +360,7 @@ export default class Application {
// the details property is decoded to transform escaped characters such as '\n' // the details property is decoded to transform escaped characters such as '\n'
const formattedError = error.response && Array.isArray(error.response.errors) && error.response.errors.map((e) => decodeURI(e.detail)); const formattedError = error.response && Array.isArray(error.response.errors) && error.response.errors.map((e) => decodeURI(e.detail));
error.alert = new Alert({ error.alert = {
type: 'error', type: 'error',
children, children,
controls: isDebug && [ controls: isDebug && [
@ -365,7 +368,7 @@ export default class Application {
Debug Debug
</Button>, </Button>,
], ],
}); };
try { try {
options.errorHandler(error); options.errorHandler(error);
@ -381,7 +384,7 @@ export default class Application {
console.groupEnd(); console.groupEnd();
} }
this.alerts.show(error.alert); this.requestErrorAlert = this.alerts.show(error.alert);
} }
deferred.reject(error); deferred.reject(error);
@ -397,7 +400,7 @@ export default class Application {
* @private * @private
*/ */
showDebug(error, formattedError) { showDebug(error, formattedError) {
this.alerts.dismiss(this.requestError.alert); this.alerts.dismiss(this.requestErrorAlert);
this.modal.show(new RequestErrorModal({ error, formattedError })); this.modal.show(new RequestErrorModal({ error, formattedError }));
} }

View File

@ -7,20 +7,16 @@ import Alert from './Alert';
*/ */
export default class AlertManager extends Component { export default class AlertManager extends Component {
init() { init() {
/** this.state = this.props.state;
* An array of Alert components which are currently showing.
*
* @type {Alert[]}
* @protected
*/
this.components = [];
} }
view() { view() {
return ( return (
<div className="AlertManager"> <div className="AlertManager">
{this.components.map((component) => ( {Object.entries(this.state.getActiveAlerts()).map(([key, alert]) => (
<div className="AlertManager-alert">{component}</div> <div className="AlertManager-alert">
{(alert.componentClass || Alert).component({ ...alert.attrs, ondismiss: this.state.dismiss.bind(this.state, key) })}
</div>
))} ))}
</div> </div>
); );
@ -32,46 +28,4 @@ export default class AlertManager extends Component {
// to be retained across route changes. // to be retained across route changes.
context.retain = true; context.retain = true;
} }
/**
* Show an Alert in the alerts area.
*
* @param {Alert} component
* @public
*/
show(component) {
if (!(component instanceof Alert)) {
throw new Error('The AlertManager component can only show Alert components');
}
component.props.ondismiss = this.dismiss.bind(this, component);
this.components.push(component);
m.redraw();
}
/**
* Dismiss an alert.
*
* @param {Alert} component
* @public
*/
dismiss(component) {
const index = this.components.indexOf(component);
if (index !== -1) {
this.components.splice(index, 1);
m.redraw();
}
}
/**
* Clear all alerts.
*
* @public
*/
clear() {
this.components = [];
m.redraw();
}
} }

View File

@ -11,16 +11,16 @@ import Button from './Button';
export default class Modal extends Component { export default class Modal extends Component {
init() { init() {
/** /**
* An alert component to show below the header. * Attributes for an alert component to show below the header.
* *
* @type {Alert} * @type {object}
*/ */
this.alert = null; this.alertAttrs = null;
} }
view() { view() {
if (this.alert) { if (this.alertAttrs) {
this.alert.props.dismissible = false; this.alertAttrs.dismissible = false;
} }
return ( return (
@ -43,7 +43,7 @@ export default class Modal extends Component {
<h3 className="App-titleControl App-titleControl--text">{this.title()}</h3> <h3 className="App-titleControl App-titleControl--text">{this.title()}</h3>
</div> </div>
{alert ? <div className="Modal-alert">{this.alert}</div> : ''} {this.alertAttrs ? <div className="Modal-alert">{Alert.component(this.alertAttrs)}</div> : ''}
{this.content()} {this.content()}
</form> </form>
@ -123,7 +123,7 @@ export default class Modal extends Component {
* @param {RequestError} error * @param {RequestError} error
*/ */
onerror(error) { onerror(error) {
this.alert = error.alert; this.alertAttrs = error.alert;
m.redraw(); m.redraw();

View File

@ -0,0 +1,50 @@
import Alert from '../components/Alert';
export default class AlertManagerState {
constructor() {
this.activeAlerts = {};
this.alertId = 0;
}
getActiveAlerts() {
return this.activeAlerts;
}
/**
* Show an Alert in the alerts area.
*/
show(attrs, componentClass = Alert) {
// Breaking Change Compliance Warning, Remove in Beta 15.
// This is applied to the first argument (attrs) because previously, the alert was passed as the first argument.
if (attrs === Alert || attrs instanceof Alert) {
// This is duplicated so that if the error is caught, an error message still shows up in the debug console.
console.error('The AlertManager can only show Alerts. Whichever extension triggered this alert should be updated to comply with beta 14.');
throw new Error('The AlertManager can only show Alerts. Whichever extension triggered this alert should be updated to comply with beta 14.');
}
// End Change Compliance Warning, Remove in Beta 15
this.activeAlerts[++this.alertId] = { attrs, componentClass };
m.redraw();
return this.alertId;
}
/**
* Dismiss an alert.
*/
dismiss(key) {
if (!key || !(key in this.activeAlerts)) return;
delete this.activeAlerts[key];
m.redraw();
}
/**
* Clear all alerts.
*
* @public
*/
clear() {
this.activeAlerts = {};
m.redraw();
}
}

View File

@ -122,7 +122,7 @@ export default class ChangeEmailModal extends Modal {
onerror(error) { onerror(error) {
if (error.status === 401) { if (error.status === 401) {
error.alert.props.children = app.translator.trans('core.forum.change_email.incorrect_password_message'); error.alert.children = app.translator.trans('core.forum.change_email.incorrect_password_message');
} }
super.onerror(error); super.onerror(error);

View File

@ -1,5 +1,4 @@
import ComposerBody from './ComposerBody'; import ComposerBody from './ComposerBody';
import Alert from '../../common/components/Alert';
import Button from '../../common/components/Button'; import Button from '../../common/components/Button';
import icon from '../../common/helpers/icon'; import icon from '../../common/helpers/icon';
@ -101,13 +100,11 @@ export default class EditPostComposer extends ComposerBody {
app.alerts.dismiss(alert); app.alerts.dismiss(alert);
}, },
}); });
app.alerts.show( alert = app.alerts.show({
(alert = new Alert({ type: 'success',
type: 'success', children: app.translator.trans('core.forum.composer_edit.edited_message'),
children: app.translator.trans('core.forum.composer_edit.edited_message'), controls: [viewButton],
controls: [viewButton], });
}))
);
} }
app.composer.hide(); app.composer.hide();

View File

@ -104,7 +104,7 @@ export default class ForgotPasswordModal extends Modal {
onerror(error) { onerror(error) {
if (error.status === 404) { if (error.status === 404) {
error.alert.props.children = app.translator.trans('core.forum.forgot_password.not_found_message'); error.alert.children = app.translator.trans('core.forum.forgot_password.not_found_message');
} }
super.onerror(error); super.onerror(error);

View File

@ -179,7 +179,7 @@ export default class LogInModal extends Modal {
onerror(error) { onerror(error) {
if (error.status === 401) { if (error.status === 401) {
error.alert.props.children = app.translator.trans('core.forum.log_in.invalid_login_message'); error.alert.children = app.translator.trans('core.forum.log_in.invalid_login_message');
} }
super.onerror(error); super.onerror(error);

View File

@ -1,5 +1,4 @@
import ComposerBody from './ComposerBody'; import ComposerBody from './ComposerBody';
import Alert from '../../common/components/Alert';
import Button from '../../common/components/Button'; import Button from '../../common/components/Button';
import icon from '../../common/helpers/icon'; import icon from '../../common/helpers/icon';
import extractText from '../../common/utils/extractText'; import extractText from '../../common/utils/extractText';
@ -104,13 +103,11 @@ export default class ReplyComposer extends ComposerBody {
app.alerts.dismiss(alert); app.alerts.dismiss(alert);
}, },
}); });
app.alerts.show( alert = app.alerts.show({
(alert = new Alert({ type: 'success',
type: 'success', children: app.translator.trans('core.forum.composer_reply.posted_message'),
children: app.translator.trans('core.forum.composer_reply.posted_message'), controls: [viewButton],
controls: [viewButton], });
}))
);
} }
app.composer.hide(); app.composer.hide();

View File

@ -1,4 +1,3 @@
import Alert from '../../common/components/Alert';
import Button from '../../common/components/Button'; import Button from '../../common/components/Button';
import Separator from '../../common/components/Separator'; import Separator from '../../common/components/Separator';
import EditUserModal from '../components/EditUserModal'; import EditUserModal from '../components/EditUserModal';
@ -134,12 +133,10 @@ export default {
error: 'core.forum.user_controls.delete_error_message', error: 'core.forum.user_controls.delete_error_message',
}[type]; }[type];
app.alerts.show( app.alerts.show({
new Alert({ type,
type, children: app.translator.trans(message, { username, email }),
children: app.translator.trans(message, { username, email }), });
})
);
}, },
/** /**