diff --git a/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx b/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx
new file mode 100644
index 00000000..373df559
--- /dev/null
+++ b/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2022 The Pybricks Authors
+
+import { Callout, Intent } from '@blueprintjs/core';
+import React, { useMemo } from 'react';
+import {
+ pybricksUsbDfuWindowsDriverInstallUrl,
+ pybricksUsbLinuxUdevRulesUrl,
+} from '../../app/constants';
+import { Hub, hubHasBluetoothButton, hubHasUSB } from '../../components/hubPicker';
+import ExternalLinkIcon from '../../utils/ExternalLinkIcon';
+import { isLinux, isWindows } from '../../utils/os';
+import { useI18n } from './i18n';
+
+type BootloaderInstructionsProps = {
+ hubType: Hub;
+};
+
+/**
+ * Provides customized instructions on how to enter bootloader mode based
+ * on the hub type.
+ */
+const BootloaderInstructions: React.VoidFunctionComponent<
+ BootloaderInstructionsProps
+> = ({ hubType }) => {
+ const i18n = useI18n();
+
+ const { button, light, lightPattern } = useMemo(() => {
+ return {
+ button: i18n.translate(
+ hubHasBluetoothButton(hubType) ? 'button.bluetooth' : 'button.power',
+ ),
+ light: i18n.translate(
+ hubHasBluetoothButton(hubType) ? 'light.bluetooth' : 'light.status',
+ ),
+ lightPattern: i18n.translate(
+ hubHasBluetoothButton(hubType)
+ ? 'lightPattern.bluetooth'
+ : 'lightPattern.status',
+ ),
+ };
+ }, [i18n, hubType]);
+
+ return (
+ <>
+ {hubHasUSB(hubType) && isLinux() && (
+
+ {i18n.translate('warning.linux')}{' '}
+
+ {i18n.translate('warning.learnMore')}
+
+
+
+ )}
+ {hubHasUSB(hubType) && isWindows() && (
+
+ {i18n.translate('warning.windows')}{' '}
+
+ {i18n.translate('warning.learnMore')}
+
+
+
+ )}
+
{i18n.translate('instruction')}
+
+ {hubHasUSB(hubType) && - {i18n.translate('step.disconnectUsb')}
}
+
+ - {i18n.translate('step.powerOff')}
+
+ {/* City hub has power issues and requires disconnecting motors/sensors */}
+ {hubType === Hub.City && - {i18n.translate('step.disconnectIo')}
}
+
+ - {i18n.translate('step.holdButton', { button })}
+
+ {hubHasUSB(hubType) && - {i18n.translate('step.connectUsb')}
}
+
+ -
+ {i18n.translate('step.waitForLight', {
+ button,
+ light,
+ lightPattern,
+ })}
+
+
+ -
+ {i18n.translate(
+ /* hubs with USB will keep the power on, but other hubs won't */
+ hubHasUSB(hubType) ? 'step.releaseButton' : 'step.keepHolding',
+ {
+ button,
+ },
+ )}
+
+
+ >
+ );
+};
+
+export default BootloaderInstructions;
diff --git a/src/firmware/bootloaderInstructions/i18n.ts b/src/firmware/bootloaderInstructions/i18n.ts
new file mode 100644
index 00000000..eb8dc486
--- /dev/null
+++ b/src/firmware/bootloaderInstructions/i18n.ts
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2022 The Pybricks Authors
+
+import { useI18n as useShopifyI18n } from '@shopify/react-i18n';
+import type { TypedI18n } from '../../i18n';
+import type translations from './translations/en.json';
+
+export function useI18n(): TypedI18n {
+ // istanbul ignore next: babel-loader rewrites this line
+ const [i18n] = useShopifyI18n();
+ return i18n;
+}
diff --git a/src/firmware/bootloaderInstructions/translations/en.json b/src/firmware/bootloaderInstructions/translations/en.json
new file mode 100644
index 00000000..b4cf9f52
--- /dev/null
+++ b/src/firmware/bootloaderInstructions/translations/en.json
@@ -0,0 +1,30 @@
+{
+ "warning": {
+ "linux": "If you have never used Pybricks with USB on Linux, you will need to configure udev rules to allow permission before you can flash the hub firmware.",
+ "windows": " If you have never used Pybricks with USB on Windows, you may need to manually install a USB driver before you can flash the hub firmware.",
+ "learnMore": "Learn more."
+ },
+ "instruction": "To flash the firmware, the hub must be placed in bootloader mode. Follow the steps below to do this:",
+ "button": {
+ "bluetooth": "Bluetooth button",
+ "power": "power button"
+ },
+ "light": {
+ "bluetooth": "Bluetooth light",
+ "status": "hub status light"
+ },
+ "lightPattern": {
+ "bluetooth": "pink-green-blue-off",
+ "status": "light purple"
+ },
+ "step": {
+ "disconnectUsb": "Disconnect the USB cable from the hub.",
+ "powerOff": "Turn off the hub.",
+ "disconnectIo": "Disconnect all motors and sensors from the I/O ports on the hub.",
+ "holdButton": "Press and hold the {button} on the hub.",
+ "connectUsb": "Connect the USB cable.",
+ "waitForLight": "Keep holding the {button} and wait for the {light} to start flashing {lightPattern}. This takes about 5 seconds.",
+ "releaseButton": "Release the {button}",
+ "keepHolding": "Keep holding the {button}."
+ }
+}
diff --git a/src/firmware/installPybricksDialog/InstallPybricksDialog.tsx b/src/firmware/installPybricksDialog/InstallPybricksDialog.tsx
index e9964f56..a301e4db 100644
--- a/src/firmware/installPybricksDialog/InstallPybricksDialog.tsx
+++ b/src/firmware/installPybricksDialog/InstallPybricksDialog.tsx
@@ -4,7 +4,6 @@
import './installPybricksDialog.scss';
import {
Button,
- Callout,
Checkbox,
Classes,
Collapse,
@@ -23,28 +22,18 @@ import { Classes as Classes2, Popover2 } from '@blueprintjs/popover2';
import { FirmwareMetadata, HubType } from '@pybricks/firmware';
import { fileOpen } from 'browser-fs-access';
import classNames from 'classnames';
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { useDispatch } from 'react-redux';
import { useLocalStorage } from 'usehooks-ts';
import { alertsShowAlert } from '../../alerts/actions';
-import {
- pybricksUsbDfuWindowsDriverInstallUrl,
- pybricksUsbLinuxUdevRulesUrl,
-} from '../../app/constants';
import HelpButton from '../../components/HelpButton';
-import {
- Hub,
- hubBootloaderType,
- hubHasBluetoothButton,
- hubHasUSB,
-} from '../../components/hubPicker';
+import { Hub, hubBootloaderType } from '../../components/hubPicker';
import { HubPicker } from '../../components/hubPicker/HubPicker';
import { useHubPickerSelectedHub } from '../../components/hubPicker/hooks';
import { useSelector } from '../../reducers';
import { ensureError } from '../../utils';
-import ExternalLinkIcon from '../../utils/ExternalLinkIcon';
-import { isLinux, isWindows } from '../../utils/os';
+import BootloaderInstructions from '../bootloaderInstructions/BootloaderInstructions';
import {
firmwareInstallPybricksDialogAccept,
firmwareInstallPybricksDialogCancel,
@@ -55,6 +44,7 @@ import { validateHubName } from '.';
const dialogBody = classNames(
Classes.DIALOG_BODY,
+ Classes.RUNNING_TEXT,
'pb-firmware-installPybricksDialog-body',
);
@@ -409,107 +399,16 @@ const BootloaderModePanel: React.VoidFunctionComponent
}) => {
const i18n = useI18n();
- const { button, light, lightPattern } = useMemo(() => {
- return {
- button: i18n.translate(
- hubHasBluetoothButton(hubType)
- ? 'bootloaderPanel.button.bluetooth'
- : 'bootloaderPanel.button.power',
- ),
- light: i18n.translate(
- hubHasBluetoothButton(hubType)
- ? 'bootloaderPanel.light.bluetooth'
- : 'bootloaderPanel.light.status',
- ),
- lightPattern: i18n.translate(
- hubHasBluetoothButton(hubType)
- ? 'bootloaderPanel.lightPattern.bluetooth'
- : 'bootloaderPanel.lightPattern.status',
- ),
- };
- }, [i18n, hubType]);
-
return (
- {hubHasUSB(hubType) && isLinux() && (
-
- {i18n.translate('bootloaderPanel.warning.linux')}{' '}
-
- {i18n.translate('bootloaderPanel.warning.learnMore')}
-
-
-
- )}
- {hubHasUSB(hubType) && isWindows() && (
-
- {i18n.translate('bootloaderPanel.warning.windows')}{' '}
-
- {i18n.translate('bootloaderPanel.warning.learnMore')}
-
-
-
- )}
-
-
-
{i18n.translate('bootloaderPanel.instruction1')}
-
- {hubHasUSB(hubType) && (
- - {i18n.translate('bootloaderPanel.step.disconnectUsb')}
- )}
-
- - {i18n.translate('bootloaderPanel.step.powerOff')}
-
- {/* City hub has power issues and requires disconnecting motors/sensors */}
- {hubType === Hub.City && (
- - {i18n.translate('bootloaderPanel.step.disconnectIo')}
- )}
-
- -
- {i18n.translate('bootloaderPanel.step.holdButton', { button })}
-
-
- {hubHasUSB(hubType) && (
- - {i18n.translate('bootloaderPanel.step.connectUsb')}
- )}
-
- -
- {i18n.translate('bootloaderPanel.step.waitForLight', {
- button,
- light,
- lightPattern,
- })}
-
-
- -
- {i18n.translate(
- /* hubs with USB will keep the power on, but other hubs won't */
- hubHasUSB(hubType)
- ? 'bootloaderPanel.step.releaseButton'
- : 'bootloaderPanel.step.keepHolding',
- {
- button,
- },
- )}
-
-
-
- {i18n.translate('bootloaderPanel.instruction2', {
- flashFirmware: (
-
- {i18n.translate('flashFirmwareButton.label')}
-
- ),
- })}
-
-
+
+
+ {i18n.translate('bootloaderPanel.instruction2', {
+ flashFirmware: (
+ {i18n.translate('flashFirmwareButton.label')}
+ ),
+ })}
+
);
};
diff --git a/src/firmware/installPybricksDialog/translations/en.json b/src/firmware/installPybricksDialog/translations/en.json
index 3f254b24..771f9637 100644
--- a/src/firmware/installPybricksDialog/translations/en.json
+++ b/src/firmware/installPybricksDialog/translations/en.json
@@ -54,34 +54,6 @@
},
"bootloaderPanel": {
"title": "Place hub in bootloader mode",
- "warning": {
- "linux": "If you have never used Pybricks with USB on Linux, you will need to configure udev rules to allow permission before you can flash the hub firmware.",
- "windows": " If you have never used Pybricks with USB on Windows, you may need to manually install a USB driver before you can flash the hub firmware.",
- "learnMore": "Learn more."
- },
- "instruction1": "To flash the firmware, the hub must be placed in bootloader mode. Follow the steps below to do this:",
- "button": {
- "bluetooth": "Bluetooth button",
- "power": "power button"
- },
- "light": {
- "bluetooth": "Bluetooth light",
- "status": "hub status light"
- },
- "lightPattern": {
- "bluetooth": "pink-green-blue-off",
- "status": "light purple"
- },
- "step": {
- "disconnectUsb": "Disconnect the USB cable from the hub.",
- "powerOff": "Turn off the hub.",
- "disconnectIo": "Disconnect all motors and sensors from the I/O ports on the hub.",
- "holdButton": "Press and hold the {button} on the hub.",
- "connectUsb": "Connect the USB cable.",
- "waitForLight": "Keep holding the {button} and wait for the {light} to start flashing {lightPattern}. This takes about 5 seconds.",
- "releaseButton": "Release the {button}",
- "keepHolding": "Keep holding the {button}."
- },
"instruction2": "Then click the {flashFirmware} button below to connect to the hub and flash the firmware."
},
"backButton": {