diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx
index 7a2edbee..3976044f 100644
--- a/src/components/Notification.tsx
+++ b/src/components/Notification.tsx
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
-// Copyright (c) 2020 The Pybricks Authors
+// Copyright (c) 2021 The Pybricks Authors
+
+// provides translation for notification text
import { Replacements, useI18n } from '@shopify/react-i18n';
import React from 'react';
import { MessageId } from './notification-i18n';
import en from './notification-i18n.en.json';
-// provides translation for notification text
-
type OwnProps = {
messageId: MessageId;
replacements?: Replacements;
diff --git a/src/components/UnexpectedErrorNotification.tsx b/src/components/UnexpectedErrorNotification.tsx
new file mode 100644
index 00000000..8f9957ea
--- /dev/null
+++ b/src/components/UnexpectedErrorNotification.tsx
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2021 The Pybricks Authors
+
+// Provides special notification contents for unexpected errors.
+
+import { AnchorButton, Button, ButtonGroup, Intent } from '@blueprintjs/core';
+import { useI18n } from '@shopify/react-i18n';
+import React from 'react';
+import { MessageId } from './notification-i18n';
+import en from './notification-i18n.en.json';
+
+type OwnProps = {
+ messageId: MessageId;
+ err: Error;
+};
+
+export default function UnexpectedErrorNotification(props: OwnProps): JSX.Element {
+ const [i18n] = useI18n({
+ id: 'notification',
+ translations: { en },
+ fallback: en,
+ });
+ const { messageId, err } = props;
+ return (
+ <>
+
{i18n.translate(messageId, { errorMessage: err.message })}
+
+
+
+
+ {i18n.translate(MessageId.ReportBug)}
+
+
+
+ >
+ );
+}
diff --git a/src/components/notification-i18n.en.json b/src/components/notification-i18n.en.json
index 90dc0233..d0ae6e09 100644
--- a/src/components/notification-i18n.en.json
+++ b/src/components/notification-i18n.en.json
@@ -1,9 +1,11 @@
{
+ "copyErrorMessage": "Copy Error Message",
+ "reportBug": "Report Bug",
"ble": {
"gattPermission": "The web browser did not give permission to use Bluetooth Low Energy",
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service. Try removing the \"{hubName}\" device in your OS Bluetooth settings, then try again.",
"noWebBluetooth": "This web browser does not support Web Bluetooth or it is not enabled.",
- "connectFailed": "Unexpected error while trying to connect. Check console log and report the error."
+ "unexpectedError": "Unexpected error while trying to connect: {errorMessage}"
},
"editor": {
"programChanged": {
diff --git a/src/components/notification-i18n.ts b/src/components/notification-i18n.ts
index e5948e18..48414268 100644
--- a/src/components/notification-i18n.ts
+++ b/src/components/notification-i18n.ts
@@ -4,7 +4,9 @@
// Notification translation keys.
export enum MessageId {
- BleConnectFailed = 'ble.connectFailed',
+ CopyErrorMessage = 'copyErrorMessage',
+ ReportBug = 'reportBug',
+ BleUnexpectedError = 'ble.unexpectedError',
BleGattPermission = 'ble.gattPermission',
BleGattServiceNotFound = 'ble.gattServiceNotFound',
BleNoWebBluetooth = 'ble.noWebBluetooth',
diff --git a/src/sagas/notification.test.ts b/src/sagas/notification.test.ts
index 5ffd07f1..9b529eb9 100644
--- a/src/sagas/notification.test.ts
+++ b/src/sagas/notification.test.ts
@@ -27,7 +27,9 @@ test.each([
reason: BleDeviceFailToConnectReasonType.Unknown,
err: { name: 'test', message: 'unknown' },
}),
- bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown),
+ bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown, {
+ message: 'test',
+ }),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoWebBluetooth),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.GattServiceNotFound),
storageChanged('test'),
diff --git a/src/sagas/notification.ts b/src/sagas/notification.ts
index d386583b..687e0e59 100644
--- a/src/sagas/notification.ts
+++ b/src/sagas/notification.ts
@@ -30,6 +30,7 @@ import { MpyActionType, MpyDidFailToCompileAction } from '../actions/mpy';
import { NotificationActionType, NotificationAddAction } from '../actions/notification';
import { ServiceWorkerActionType } from '../actions/service-worker';
import Notification from '../components/Notification';
+import UnexpectedErrorNotification from '../components/UnexpectedErrorNotification';
import { MessageId } from '../components/notification-i18n';
import { appName } from '../settings/ui';
@@ -140,6 +141,17 @@ function* showSingleton(
);
}
+/** Shows a special notification for unexpected errors. */
+function* showUnexpectedError(messageId: MessageId, err: Error): Generator {
+ const { toaster } = (yield getContext('notification')) as NotificationContext;
+ toaster.show({
+ intent: mapIntent(Level.Error),
+ icon: mapIcon(Level.Error),
+ message: React.createElement(UnexpectedErrorNotification, { messageId, err }),
+ timeout: 0,
+ });
+}
+
function* showBleDeviceDidFailToConnectError(
action: BleDeviceDidFailToConnectAction,
): Generator {
@@ -165,7 +177,7 @@ function* showBleDeviceDidFailToConnectError(
);
break;
case BleDeviceFailToConnectReasonType.Unknown:
- yield* showSingleton(Level.Error, MessageId.BleConnectFailed);
+ yield* showUnexpectedError(MessageId.BleUnexpectedError, action.err);
break;
}
}
@@ -191,7 +203,7 @@ function* showBootloaderDidFailToConnectError(
);
break;
case BootloaderConnectionFailureReason.Unknown:
- yield* showSingleton(Level.Error, MessageId.BleConnectFailed);
+ yield* showUnexpectedError(MessageId.BleUnexpectedError, action.err);
break;
}
}