mirror of https://github.com/mastodon/mastodon
Refactor alerts to TypeScript, remove `react-notification` dependency (#34239)
parent
e1dbbf6c9d
commit
94d71c992e
@ -0,0 +1,105 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { useIntl } from 'react-intl';
|
||||
import type { IntlShape } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { dismissAlert } from 'mastodon/actions/alerts';
|
||||
import type {
|
||||
Alert,
|
||||
TranslatableString,
|
||||
TranslatableValues,
|
||||
} from 'mastodon/models/alert';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const formatIfNeeded = (
|
||||
intl: IntlShape,
|
||||
message: TranslatableString,
|
||||
values?: TranslatableValues,
|
||||
) => {
|
||||
if (typeof message === 'object') {
|
||||
return intl.formatMessage(message, values);
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
const Alert: React.FC<{
|
||||
alert: Alert;
|
||||
dismissAfter: number;
|
||||
}> = ({
|
||||
alert: { key, title, message, values, action, onClick },
|
||||
dismissAfter,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const setActiveTimeout = setTimeout(() => {
|
||||
setActive(true);
|
||||
}, 1);
|
||||
|
||||
return () => {
|
||||
clearTimeout(setActiveTimeout);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const dismissTimeout = setTimeout(() => {
|
||||
setActive(false);
|
||||
|
||||
// Allow CSS transition to finish before removing from the DOM
|
||||
setTimeout(() => {
|
||||
dispatch(dismissAlert({ key }));
|
||||
}, 500);
|
||||
}, dismissAfter);
|
||||
|
||||
return () => {
|
||||
clearTimeout(dismissTimeout);
|
||||
};
|
||||
}, [dispatch, setActive, key, dismissAfter]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('notification-bar', {
|
||||
'notification-bar-active': active,
|
||||
})}
|
||||
>
|
||||
<div className='notification-bar-wrapper'>
|
||||
{title && (
|
||||
<span className='notification-bar-title'>
|
||||
{formatIfNeeded(intl, title, values)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span className='notification-bar-message'>
|
||||
{formatIfNeeded(intl, message, values)}
|
||||
</span>
|
||||
|
||||
{action && (
|
||||
<button className='notification-bar-action' onClick={onClick}>
|
||||
{formatIfNeeded(intl, action, values)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AlertsController: React.FC = () => {
|
||||
const alerts = useAppSelector((state) => state.alerts);
|
||||
|
||||
if (alerts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='notification-list'>
|
||||
{alerts.map((alert, idx) => (
|
||||
<Alert key={alert.key} alert={alert} dismissAfter={5000 + idx * 1000} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { injectIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { NotificationStack } from 'react-notification';
|
||||
|
||||
import { dismissAlert } from 'mastodon/actions/alerts';
|
||||
import { getAlerts } from 'mastodon/selectors';
|
||||
|
||||
const mapStateToProps = (state, { intl }) => ({
|
||||
notifications: getAlerts(state, { intl }),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onDismiss (alert) {
|
||||
dispatch(dismissAlert(alert));
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack));
|
||||
@ -0,0 +1,14 @@
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
|
||||
export type TranslatableString = string | MessageDescriptor;
|
||||
|
||||
export type TranslatableValues = Record<string, string | number | Date>;
|
||||
|
||||
export interface Alert {
|
||||
key: number;
|
||||
title?: TranslatableString;
|
||||
message: TranslatableString;
|
||||
action?: TranslatableString;
|
||||
values?: TranslatableValues;
|
||||
onClick?: () => void;
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import {
|
||||
ALERT_SHOW,
|
||||
ALERT_DISMISS,
|
||||
ALERT_CLEAR,
|
||||
} from '../actions/alerts';
|
||||
|
||||
const initialState = ImmutableList([]);
|
||||
|
||||
let id = 0;
|
||||
|
||||
const addAlert = (state, alert) =>
|
||||
state.push({
|
||||
key: id++,
|
||||
...alert,
|
||||
});
|
||||
|
||||
export default function alerts(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ALERT_SHOW:
|
||||
return addAlert(state, action.alert);
|
||||
case ALERT_DISMISS:
|
||||
return state.filterNot(item => item.key === action.alert.key);
|
||||
case ALERT_CLEAR:
|
||||
return state.clear();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
|
||||
import { showAlert, dismissAlert, clearAlerts } from 'mastodon/actions/alerts';
|
||||
import type { Alert } from 'mastodon/models/alert';
|
||||
|
||||
const initialState: Alert[] = [];
|
||||
|
||||
let id = 0;
|
||||
|
||||
export const alertsReducer = createReducer(initialState, (builder) => {
|
||||
builder
|
||||
.addCase(showAlert, (state, { payload }) => {
|
||||
state.push({
|
||||
key: id++,
|
||||
...payload,
|
||||
});
|
||||
})
|
||||
.addCase(dismissAlert, (state, { payload: { key } }) => {
|
||||
return state.filter((item) => item.key !== key);
|
||||
})
|
||||
.addCase(clearAlerts, () => {
|
||||
return [];
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue