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": {