mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-14 18:46:17 +00:00
convert notifications to static API
This converts notifications from a reducer to a saga. This way, our state doesn't get bogged down with a bunch of notifications.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021 The Pybricks Authors
|
||||
|
||||
import { IToaster, Toaster } from '@blueprintjs/core';
|
||||
import { I18nContext, I18nManager } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
/**
|
||||
* Creates an `IToaster` for static usage similar to `Toaster.create()` except
|
||||
* that it is wrapped in an `I18nContext.Provider` so that messages can be
|
||||
* translated.
|
||||
*
|
||||
* @param i18n The i18n manager object.
|
||||
*/
|
||||
export function create(i18n: I18nManager): IToaster {
|
||||
const containerElement = document.createElement('div');
|
||||
|
||||
document.body.appendChild(containerElement);
|
||||
|
||||
const toaster = React.createRef<Toaster>();
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nContext.Provider value={i18n}>
|
||||
<Toaster usePortal={false} ref={toaster} />
|
||||
</I18nContext.Provider>,
|
||||
containerElement,
|
||||
);
|
||||
|
||||
// istanbul ignore if: should not happen since we are rendering the component
|
||||
if (toaster.current === null) {
|
||||
throw new Error('failed to set toaster ref');
|
||||
}
|
||||
|
||||
return toaster.current;
|
||||
}
|
||||
+12
-109
@@ -1,121 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { IconName, Intent, Toast } from '@blueprintjs/core';
|
||||
import { Replacements, WithI18nProps, withI18n } from '@shopify/react-i18n';
|
||||
import { Replacements, useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Action, Dispatch } from '../actions';
|
||||
import { NotificationLevel, remove } from '../actions/notification';
|
||||
import { Level, MessageAction } from '../reducers/notification';
|
||||
import { MessageId } from './notification-i18n';
|
||||
import en from './notification-i18n.en.json';
|
||||
|
||||
interface DispatchProps {
|
||||
onAction: (action: Action) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
// provides translation for notification text
|
||||
|
||||
interface OwnProps {
|
||||
id: number;
|
||||
level: NotificationLevel;
|
||||
message?: string;
|
||||
messageId?: MessageId;
|
||||
type OwnProps = {
|
||||
messageId: MessageId;
|
||||
replacements?: Replacements;
|
||||
helpUrl?: string;
|
||||
action?: MessageAction;
|
||||
}
|
||||
};
|
||||
|
||||
type NotificationProps = DispatchProps & OwnProps & WithI18nProps;
|
||||
|
||||
function mapIntent(level: NotificationLevel): Intent {
|
||||
switch (level) {
|
||||
case Level.Error:
|
||||
return Intent.DANGER;
|
||||
case Level.Warning:
|
||||
return Intent.WARNING;
|
||||
case Level.Info:
|
||||
return Intent.PRIMARY;
|
||||
default:
|
||||
return Intent.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
function mapIcon(level: NotificationLevel): IconName | undefined {
|
||||
switch (level) {
|
||||
case Level.Error:
|
||||
return 'error';
|
||||
case Level.Warning:
|
||||
return 'warning-sign';
|
||||
case Level.Info:
|
||||
return 'info-sign';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class Notification extends React.Component<NotificationProps> {
|
||||
render(): JSX.Element {
|
||||
const {
|
||||
action,
|
||||
helpUrl,
|
||||
i18n,
|
||||
level,
|
||||
message,
|
||||
messageId,
|
||||
onAction,
|
||||
onClose,
|
||||
replacements,
|
||||
} = this.props;
|
||||
return (
|
||||
<Toast
|
||||
onDismiss={(): void => onClose()}
|
||||
timeout={0}
|
||||
intent={mapIntent(level)}
|
||||
icon={mapIcon(level)}
|
||||
message={
|
||||
<div>
|
||||
<p>
|
||||
{messageId
|
||||
? i18n.translate(messageId, replacements)
|
||||
: message || 'missing message!'}
|
||||
</p>
|
||||
{helpUrl && (
|
||||
<p>
|
||||
<a
|
||||
href={helpUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
More info
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
action={
|
||||
action && {
|
||||
text: i18n.translate(action.titleId),
|
||||
onClick: (): void => onAction(action.action),
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps): DispatchProps => ({
|
||||
onAction: (a): Action => dispatch(a),
|
||||
onClose: (): Action => dispatch(remove(ownProps.id)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps,
|
||||
)(
|
||||
withI18n({
|
||||
export default function Notification(props: OwnProps): JSX.Element {
|
||||
const [i18n] = useI18n({
|
||||
id: 'notification',
|
||||
fallback: en,
|
||||
translations: { en },
|
||||
})(Notification),
|
||||
);
|
||||
fallback: en,
|
||||
});
|
||||
const { messageId, replacements } = props;
|
||||
return <>{i18n.translate(messageId, replacements)}</>;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { Toaster } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { RootState } from '../reducers';
|
||||
import { NotificationList } from '../reducers/notification';
|
||||
import Notification from './Notification';
|
||||
|
||||
interface StateProps {
|
||||
list: NotificationList;
|
||||
}
|
||||
|
||||
type NotificationStackProps = StateProps;
|
||||
|
||||
class NotificationStack extends React.Component<NotificationStackProps> {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Toaster>
|
||||
{this.props.list.map((n) => (
|
||||
<Notification
|
||||
id={n.id}
|
||||
key={n.id}
|
||||
level={n.level}
|
||||
message={n.message}
|
||||
messageId={n.messageId}
|
||||
replacements={n.replacements}
|
||||
helpUrl={n.helpUrl}
|
||||
action={n.action}
|
||||
/>
|
||||
))}
|
||||
</Toaster>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
list: state.notification.list,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(NotificationStack);
|
||||
@@ -7,7 +7,10 @@
|
||||
},
|
||||
"editor": {
|
||||
"programChanged": "The program was changed in another window. Do you want to delete this program and replace it with the new program?",
|
||||
"yesReloadProgram": "Yes"
|
||||
"yesReloadProgram": "Reload"
|
||||
},
|
||||
"mpy": {
|
||||
"error": "{errorMessage}"
|
||||
},
|
||||
"serviceWorker": {
|
||||
"success": "Content is cached for offline use.",
|
||||
|
||||
@@ -12,4 +12,5 @@ export enum MessageId {
|
||||
ServiceWorkerSuccess = 'serviceWorker.success',
|
||||
ServiceWorkerUpdate = 'serviceWorker.update',
|
||||
YesReloadProgram = 'editor.yesReloadProgram',
|
||||
MpyError = 'mpy.error',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user