From e7366c8afdfa05faed585ef08917650e942139e1 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 20 May 2022 19:25:18 -0500 Subject: [PATCH] rework alerts This starts moving alerts to the same subsystem where they are relevant instead of putting everything in notifications. So far, only the explorer file in use error is handled like this. --- src/alerts.ts | 84 +++++++++++ .../UnexpectedErrorAlert.scss} | 2 +- .../UnexpectedErrorAlert.tsx} | 35 +++-- src/alerts/actions.ts | 46 ++++++ src/alerts/alerts.ts | 7 + src/alerts/i18n.test.ts | 12 ++ src/alerts/i18n.ts | 9 ++ src/alerts/sagas.test.ts | 115 ++++++++++++++ src/alerts/sagas.ts | 46 ++++++ src/alerts/translations/en.json | 6 + src/editor/error.ts | 10 ++ src/editor/sagas.test.ts | 142 +++++++++++------- src/editor/sagas.ts | 19 ++- src/explorer/Explorer.test.tsx | 6 +- src/explorer/Explorer.tsx | 4 +- src/explorer/actions.ts | 23 +-- src/explorer/alerts/FileInUseAlert.test.tsx | 21 +++ src/explorer/alerts/FileInUseAlert.tsx | 32 ++++ src/explorer/alerts/i18n.test.ts | 12 ++ src/explorer/alerts/i18n.ts | 6 + src/explorer/alerts/index.ts | 7 + src/explorer/alerts/translations/en.json | 5 + src/explorer/sagas.test.ts | 32 +++- src/explorer/sagas.ts | 38 +++-- .../I18nToaster.tsx => i18nToaster.tsx} | 25 ++- src/index.tsx | 3 +- src/notifications/i18n.ts | 3 - src/notifications/sagas.test.ts | 4 +- src/notifications/sagas.ts | 25 +-- src/notifications/translations/en.json | 3 - src/sagas.ts | 5 +- 31 files changed, 649 insertions(+), 138 deletions(-) create mode 100644 src/alerts.ts rename src/{notifications/UnexpectedErrorNotification.scss => alerts/UnexpectedErrorAlert.scss} (86%) rename src/{notifications/UnexpectedErrorNotification.tsx => alerts/UnexpectedErrorAlert.tsx} (68%) create mode 100644 src/alerts/actions.ts create mode 100644 src/alerts/alerts.ts create mode 100644 src/alerts/i18n.test.ts create mode 100644 src/alerts/i18n.ts create mode 100644 src/alerts/sagas.test.ts create mode 100644 src/alerts/sagas.ts create mode 100644 src/alerts/translations/en.json create mode 100644 src/editor/error.ts create mode 100644 src/explorer/alerts/FileInUseAlert.test.tsx create mode 100644 src/explorer/alerts/FileInUseAlert.tsx create mode 100644 src/explorer/alerts/i18n.test.ts create mode 100644 src/explorer/alerts/i18n.ts create mode 100644 src/explorer/alerts/index.ts create mode 100644 src/explorer/alerts/translations/en.json rename src/{notifications/I18nToaster.tsx => i18nToaster.tsx} (55%) diff --git a/src/alerts.ts b/src/alerts.ts new file mode 100644 index 00000000..f05a8134 --- /dev/null +++ b/src/alerts.ts @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +import { IToastProps } from '@blueprintjs/core'; +import alerts from './alerts/alerts'; +import explorer from './explorer/alerts'; +import { CreateToast } from './i18nToaster'; + +/** This collects alerts from all of the subsystems of the app */ +const alertDomains = { + alerts, + explorer, +}; + +/** Gets the type of available alert domains. */ +export type AlertDomain = keyof typeof alertDomains; + +/** + * Gets the type of available specific alerts for a domain. + * @template D The domain. + */ +export type AlertSpecific = keyof typeof alertDomains[D]; + +/** + * Gets the instance type of the object in the lookup table. + * @template D The domain. + * @template S The specific instance name in the domain. + */ +type AlertInstance< + D extends AlertDomain, + S extends AlertSpecific, +> = typeof alertDomains[D][S] extends CreateToast + ? CreateToast + : never; + +/** + * Gets the type of the `onAlert` callback for a specific instance in the lookup table. + * @template D The domain. + * @template S The specific instance name in the domain. + */ +export type AlertCallback< + D extends AlertDomain, + S extends AlertSpecific, +> = Parameters>[0]; + +/** + * Gets the type of available actions for a specific instance in the lookup table. + * @template D The domain. + * @template S The specific instance name in the domain. + */ +export type AlertActions> = + | Parameters>[0]>[0]; + +/** + * Gets the type of the properties for a specific instance in the lookup table. + * @template D The domain. + * @template S The specific instance name in the domain. + */ +export type AlertProps> = Parameters< + AlertInstance +>[1]; + +/** + * Gets the alert creation function from the lookup table and uses it to create + * a new alert (toast). + * + * @param domain The alert domain (app subsystem). + * @param specific The specific alert for the domain. + * @param onAlert The callback that will be called when the alert is dismissed. + * @param props Any additional properties required by this specific alert. + * @returns The newly created alert properties. + */ +export function getAlertProps>( + domain: D, + specific: S, + onAlert: AlertCallback, + props: AlertProps, +): IToastProps { + const create = alertDomains[domain][specific] as unknown as CreateToast< + Record | never, + string + >; + return create(onAlert, props); +} diff --git a/src/notifications/UnexpectedErrorNotification.scss b/src/alerts/UnexpectedErrorAlert.scss similarity index 86% rename from src/notifications/UnexpectedErrorNotification.scss rename to src/alerts/UnexpectedErrorAlert.scss index 286a790d..b7302997 100644 --- a/src/notifications/UnexpectedErrorNotification.scss +++ b/src/alerts/UnexpectedErrorAlert.scss @@ -3,7 +3,7 @@ @use '@blueprintjs/core/lib/scss/variables' as bp; -pre.pb-notification-stack-trace { +pre.pb-alerts-stack-trace { max-width: bp.$pt-grid-size * 50; max-height: bp.$pt-grid-size * 50; overflow: auto; diff --git a/src/notifications/UnexpectedErrorNotification.tsx b/src/alerts/UnexpectedErrorAlert.tsx similarity index 68% rename from src/notifications/UnexpectedErrorNotification.tsx rename to src/alerts/UnexpectedErrorAlert.tsx index 7880d929..f1938484 100644 --- a/src/notifications/UnexpectedErrorNotification.tsx +++ b/src/alerts/UnexpectedErrorAlert.tsx @@ -1,23 +1,21 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2021-2022 The Pybricks Authors +// Copyright (c) 2022 The Pybricks Authors -// Provides special notification contents for unexpected errors. - -import './UnexpectedErrorNotification.scss'; +import './UnexpectedErrorAlert.scss'; import { AnchorButton, Button, ButtonGroup, Collapse, Intent } from '@blueprintjs/core'; import { useI18n } from '@shopify/react-i18n'; import React, { useState } from 'react'; import { useId } from 'react-aria'; +import { CreateToast } from '../i18nToaster'; import { I18nId } from './i18n'; -type UnexpectedErrorNotificationProps = { - messageId: I18nId; - err: Error; +type UnexpectedErrorAlertProps = { + error: Error; }; -const UnexpectedErrorNotification: React.VoidFunctionComponent< - UnexpectedErrorNotificationProps -> = ({ messageId, err }) => { +const UnexpectedErrorAlert: React.VoidFunctionComponent = ({ + error, +}) => { // istanbul ignore next: babel-loader rewrites this line const [i18n] = useI18n(); const [isExpanded, setIsExpanded] = useState(false); @@ -25,7 +23,7 @@ const UnexpectedErrorNotification: React.VoidFunctionComponent< return ( <> -

{i18n.translate(messageId, { errorMessage: err.message })}

+

{i18n.translate(I18nId.Message, { errorMessage: error.message })}