diff --git a/src/ble/alerts/BluetoothNotAvailable.tsx b/src/ble/alerts/BluetoothNotAvailable.tsx
new file mode 100644
index 00000000..91db9822
--- /dev/null
+++ b/src/ble/alerts/BluetoothNotAvailable.tsx
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2022 The Pybricks Authors
+
+import { Intent } from '@blueprintjs/core';
+import React from 'react';
+import { CreateToast } from '../../i18nToaster';
+import { I18nId, useI18n } from './i18n';
+
+const BluetoothNotAvailable: React.VoidFunctionComponent = () => {
+ const i18n = useI18n();
+ return (
+ <>
+
{i18n.translate(I18nId.BluetoothNotAvailableMessage)}
+ {i18n.translate(I18nId.BluetoothNotAvailableSuggestion)}
+ >
+ );
+};
+
+export const bluetoothNotAvailable: CreateToast = (onAction) => {
+ return {
+ message: ,
+ icon: 'error',
+ intent: Intent.DANGER,
+ onDismiss: () => onAction('dismiss'),
+ };
+};
diff --git a/src/ble/alerts/i18n.ts b/src/ble/alerts/i18n.ts
index e488990f..192a6ca8 100644
--- a/src/ble/alerts/i18n.ts
+++ b/src/ble/alerts/i18n.ts
@@ -13,4 +13,6 @@ export enum I18nId {
NoWebBluetoothMessage = 'noWebBluetooth.message',
NoWebBluetoothSuggestion = 'noWebBluetooth.suggestion',
NoWebBluetoothLinux = 'noWebBluetooth.linux',
+ BluetoothNotAvailableMessage = 'bluetoothNotAvailable.message',
+ BluetoothNotAvailableSuggestion = 'bluetoothNotAvailable.suggestion',
}
diff --git a/src/ble/alerts/index.ts b/src/ble/alerts/index.ts
index 9eda80a7..53b93b33 100644
--- a/src/ble/alerts/index.ts
+++ b/src/ble/alerts/index.ts
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
+import { bluetoothNotAvailable } from './BluetoothNotAvailable';
import { noWebBluetooth } from './NoWebBluetooth';
// gathers all of the alert creation functions for passing up to the top level
-export default { noWebBluetooth };
+export default { bluetoothNotAvailable, noWebBluetooth };
diff --git a/src/ble/alerts/translations/en.json b/src/ble/alerts/translations/en.json
index 2ae6b9cf..22090626 100644
--- a/src/ble/alerts/translations/en.json
+++ b/src/ble/alerts/translations/en.json
@@ -4,5 +4,9 @@
"suggestion": "Use a supported browser such as Google Chrome or Microsoft Edge.",
"linux": "Web Bluetooth is experimental on Linux and must be manually enabled. Copy the link below and paste it in the address bar.",
"action": "More Info"
+ },
+ "bluetoothNotAvailable": {
+ "message": "No Bluetooth adapter could be found.",
+ "suggestion": "Please connect or enable a Bluetooth Low Energy adapter and restart the browser."
}
}
diff --git a/src/ble/sagas.test.ts b/src/ble/sagas.test.ts
index 849b7a35..7d567ba7 100644
--- a/src/ble/sagas.test.ts
+++ b/src/ble/sagas.test.ts
@@ -264,6 +264,9 @@ describe('connect action is dispatched', () => {
await runConnectUntil(saga, ConnectRunPoint.Connect);
+ await expect(saga.take()).resolves.toEqual(
+ alertsShowAlert('ble', 'bluetoothNotAvailable'),
+ );
await expect(saga.take()).resolves.toEqual(
bleDidFailToConnectPybricks({
reason: BleDeviceFailToConnectReasonType.NoBluetooth,
diff --git a/src/ble/sagas.ts b/src/ble/sagas.ts
index 5e39ba37..86c844bb 100644
--- a/src/ble/sagas.ts
+++ b/src/ble/sagas.ts
@@ -109,6 +109,7 @@ function* handleBleConnectPybricks(): Generator {
const available = yield* call(() => navigator.bluetooth.getAvailability());
if (!available) {
+ yield* put(alertsShowAlert('ble', 'bluetoothNotAvailable'));
yield* put(bleDidFailToConnectPybricks({ reason: Reason.NoBluetooth }));
return;
}
diff --git a/src/lwp3-bootloader/sagas-ble.ts b/src/lwp3-bootloader/sagas-ble.ts
index 1d5f8074..8792e747 100644
--- a/src/lwp3-bootloader/sagas-ble.ts
+++ b/src/lwp3-bootloader/sagas-ble.ts
@@ -50,6 +50,7 @@ function* handleConnect(): Generator {
const available = yield* call(() => navigator.bluetooth.getAvailability());
if (!available) {
+ yield* put(alertsShowAlert('ble', 'bluetoothNotAvailable'));
yield* put(didFailToConnect(Reason.NoBluetooth));
return;
}
diff --git a/src/notifications/i18n.ts b/src/notifications/i18n.ts
index b8977bc3..c6354e29 100644
--- a/src/notifications/i18n.ts
+++ b/src/notifications/i18n.ts
@@ -16,7 +16,6 @@ export enum I18nId {
BleUnexpectedError = 'ble.unexpectedError',
BleGattPermission = 'ble.gattPermission',
BleGattServiceNotFound = 'ble.gattServiceNotFound',
- BleNoBluetooth = 'ble.noBluetooth',
EditorFailedToOpenFile = 'editor.failedToOpenFile',
EditorFailedToSaveFile = 'editor.failedToSaveFile',
ExplorerFailedToImportFiles = 'explorer.failedToImportFiles',
diff --git a/src/notifications/sagas.test.ts b/src/notifications/sagas.test.ts
index 9bf92d8e..4f022c5d 100644
--- a/src/notifications/sagas.test.ts
+++ b/src/notifications/sagas.test.ts
@@ -61,9 +61,6 @@ function createTestToasterSaga(): { toaster: IToaster; saga: AsyncSaga } {
}
test.each([
- bleDidFailToConnectPybricks({
- reason: BleDeviceFailToConnectReasonType.NoBluetooth,
- }),
bleDidFailToConnectPybricks({ reason: BleDeviceFailToConnectReasonType.NoGatt }),
bleDidFailToConnectPybricks({
reason: BleDeviceFailToConnectReasonType.NoDeviceInfoService,
@@ -78,7 +75,6 @@ test.each([
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Unknown, {
message: 'test',
}),
- bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoBluetooth),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.GattServiceNotFound),
didFailToCompile(['reason']),
add('warning', 'message'),
@@ -134,8 +130,12 @@ test.each([
bleDidFailToConnectPybricks({
reason: BleDeviceFailToConnectReasonType.NoWebBluetooth,
}),
+ bleDidFailToConnectPybricks({
+ reason: BleDeviceFailToConnectReasonType.NoBluetooth,
+ }),
bleDidFailToConnectPybricks({ reason: BleDeviceFailToConnectReasonType.Canceled }),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoWebBluetooth),
+ bootloaderDidFailToConnect(BootloaderConnectionFailureReason.NoBluetooth),
bootloaderDidFailToConnect(BootloaderConnectionFailureReason.Canceled),
didFailToFinish(FailToFinishReasonType.FailedToConnect),
serviceWorkerDidSucceed(),
diff --git a/src/notifications/sagas.ts b/src/notifications/sagas.ts
index b5871dd1..fc0ff50b 100644
--- a/src/notifications/sagas.ts
+++ b/src/notifications/sagas.ts
@@ -190,9 +190,6 @@ function* showBleDeviceDidFailToConnectError(
hubName: 'Pybricks Hub',
});
break;
- case BleDeviceFailToConnectReasonType.NoBluetooth:
- yield* showSingleton(Level.Error, I18nId.BleNoBluetooth);
- break;
case BleDeviceFailToConnectReasonType.Unknown:
yield* showUnexpectedError(I18nId.BleUnexpectedError, action.err);
break;
@@ -209,9 +206,6 @@ function* showBootloaderDidFailToConnectError(
hubName: 'LEGO Bootloader',
});
break;
- case BootloaderConnectionFailureReason.NoBluetooth:
- yield* showSingleton(Level.Error, I18nId.BleNoBluetooth);
- break;
case BootloaderConnectionFailureReason.Unknown:
yield* showUnexpectedError(I18nId.BleUnexpectedError, action.err);
break;
diff --git a/src/notifications/translations/en.json b/src/notifications/translations/en.json
index f03ca51a..798a726f 100644
--- a/src/notifications/translations/en.json
+++ b/src/notifications/translations/en.json
@@ -5,7 +5,6 @@
"ble": {
"gattPermission": "The web browser did not give permission to use Bluetooth Low Energy",
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service.\nEnsure that you are using the most recent firmware.\nIf the problem persists, try removing the \"{hubName}\" device in your OS Bluetooth settings, then try connecting again.",
- "noBluetooth": "No Bluetooth adapter could be found. Please connect or enable a Bluetooth Low Energy adapter and restart the browser.",
"unexpectedError": "Unexpected error while trying to connect: {errorMessage}"
},
"editor": {