Skip to content

Commit

Permalink
bootloaderInstructions: add videos
Browse files Browse the repository at this point in the history
This adds videos to show how to enter bootloader mode.

Issue: pybricks/support#728
  • Loading branch information
dlech committed Dec 9, 2022
1 parent be4a18b commit 0f871c6
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

## [Unreleased]

### Added
- Added videos to firmware flash dialogs ([support#728]).

### Changed
- Moved documentation show/hide button from settings to editor area ([support#778]).

Expand All @@ -14,6 +17,7 @@
- Fixed slow firmware flash on Android ([support#438]).

[support#438]: https://github.com/pybricks/support/issues/438
[support#728]: https://github.com/pybricks/support/issues/728
[support#778]: https://github.com/pybricks/support/issues/778
[support#807]: https://github.com/pybricks/support/issues/807
[support#823]: https://github.com/pybricks/support/issues/823
Expand Down
167 changes: 150 additions & 17 deletions src/firmware/bootloaderInstructions/BootloaderInstructions.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors

import './bootloaderInstructions.scss';
import { Callout, Intent } from '@blueprintjs/core';
import React, { useMemo } from 'react';
import classNames from 'classnames';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
pybricksUsbDfuWindowsDriverInstallUrl,
pybricksUsbLinuxUdevRulesUrl,
} from '../../app/constants';
import ExternalLinkIcon from '../../components/ExternalLinkIcon';
import { Hub, hubHasBluetoothButton, hubHasUSB } from '../../components/hubPicker';
import { isLinux, isWindows } from '../../utils/os';
import cityHubMp4 from './assets/bootloader-cityhub-540.mp4';
import cityHubVtt from './assets/bootloader-cityhub-metadata.vtt';
import essentialHubMp4 from './assets/bootloader-essentialhub-540.mp4';
import essentialHubVtt from './assets/bootloader-essentialhub-metadata.vtt';
import inventorHubMp4 from './assets/bootloader-inventorhub-540.mp4';
import inventorHubVtt from './assets/bootloader-inventorhub-metadata.vtt';
import moveHubMp4 from './assets/bootloader-movehub-540.mp4';
import moveHubVtt from './assets/bootloader-movehub-metadata.vtt';
import primeHubMp4 from './assets/bootloader-primehub-540.mp4';
import primeHubVtt from './assets/bootloader-primehub-metadata.vtt';
import technicHubMp4 from './assets/bootloader-technichub-540.mp4';
import technicHubVtt from './assets/bootloader-technichub-metadata.vtt';
import { useI18n } from './i18n';

type BootloaderInstructionsProps = {
hubType: Hub;
};

const videoFileMap: ReadonlyMap<Hub, string> = new Map([
[Hub.City, cityHubMp4],
[Hub.Essential, essentialHubMp4],
[Hub.Inventor, inventorHubMp4],
[Hub.Move, moveHubMp4],
[Hub.Prime, primeHubMp4],
[Hub.Technic, technicHubMp4],
]);

const metadataFileMap: ReadonlyMap<Hub, string> = new Map([
[Hub.City, cityHubVtt],
[Hub.Essential, essentialHubVtt],
[Hub.Inventor, inventorHubVtt],
[Hub.Move, moveHubVtt],
[Hub.Prime, primeHubVtt],
[Hub.Technic, technicHubVtt],
]);

/**
* Provides customized instructions on how to enter bootloader mode based
* on the hub type.
Expand All @@ -41,6 +73,36 @@ const BootloaderInstructions: React.VoidFunctionComponent<
};
}, [i18n, hubType]);

const metadataTrackRef = useRef<HTMLTrackElement>(null);
const [activeStep, setActiveStep] = useState('');

useEffect(() => {
const element = metadataTrackRef.current;

// istanbul ignore if: should not happen
if (element === null) {
return;
}

// istanbul ignore else: jsdom doesn't support video
if (process.env.NODE_ENV === 'test') {
return;
}

const handleCueChange = (e: Event) => {
const track = e.target as TextTrack;
setActiveStep(track.activeCues?.[0]?.id ?? '');
};

element.track.addEventListener('cuechange', handleCueChange);
element.track.mode = 'hidden';

return () => {
element.track.removeEventListener('cuechange', handleCueChange);
element.track.mode = 'disabled';
};
}, [setActiveStep]);

return (
<>
{hubHasUSB(hubType) && isLinux() && (
Expand Down Expand Up @@ -69,36 +131,107 @@ const BootloaderInstructions: React.VoidFunctionComponent<
<ExternalLinkIcon />
</Callout>
)}
<p>{i18n.translate('instruction')}</p>
<ol>
{hubHasUSB(hubType) && <li>{i18n.translate('step.disconnectUsb')}</li>}

<li>{i18n.translate('step.powerOff')}</li>
<video
controls
controlsList="nodownload nofullscreen"
muted
disablePictureInPicture
className="pb-bootloader-video"
>
<source src={videoFileMap.get(hubType)} type="video/mp4" />
<track
kind="metadata"
src={metadataFileMap.get(hubType)}
ref={metadataTrackRef}
/>
</video>

<div className="pb-spacer" />

<p>
{i18n.translate('instruction', {
startPoweredOff: hubHasUSB(hubType)
? i18n.translate('startPoweredOff.usb')
: i18n.translate('startPoweredOff.default'),
})}
</p>
<ol>
{/* City hub has power issues and requires disconnecting motors/sensors */}
{hubType === Hub.City && <li>{i18n.translate('step.disconnectIo')}</li>}
{hubType === Hub.City && (
<li
className={classNames(
activeStep === 'disconnect-io' && 'pb-active-step',
)}
>
{i18n.translate('step.disconnectIo')}
</li>
)}

<li>{i18n.translate('step.holdButton', { button })}</li>
<li
className={classNames(
activeStep === 'hold-button' && 'pb-active-step',
)}
>
{i18n.translate('step.holdButton', { button })}
</li>

{hubHasUSB(hubType) && <li>{i18n.translate('step.connectUsb')}</li>}
{/* not strictly necessary, but order is swapped in the video,
so we match it here. */}
{hubType !== Hub.Essential && hubHasUSB(hubType) && (
<li
className={classNames(
activeStep === 'connect-usb' && 'pb-active-step',
)}
>
{i18n.translate('step.connectUsb')}
</li>
)}

<li>
<li
className={classNames(
activeStep === 'wait-for-light' && 'pb-active-step',
)}
>
{i18n.translate('step.waitForLight', {
button,
light,
lightPattern,
})}
</li>

<li>
{i18n.translate(
/* hubs with USB will keep the power on, but other hubs won't */
hubHasUSB(hubType) ? 'step.releaseButton' : 'step.keepHolding',
{
{hubType === Hub.Essential && hubHasUSB(hubType) && (
<li
className={classNames(
activeStep === 'connect-usb' && 'pb-active-step',
)}
>
{i18n.translate('step.connectUsb')}
</li>
)}

{/* hubs with USB will keep the power on, but other hubs won't */}
{hubHasUSB(hubType) ? (
<li
className={classNames(
activeStep === 'release-button' && 'pb-active-step',
)}
>
{i18n.translate('step.releaseButton', {
button,
},
)}
</li>
})}
</li>
) : (
<li
className={classNames(
activeStep === 'keep-holding' && 'pb-active-step',
)}
>
{i18n.translate('step.keepHolding', {
button,
})}
</li>
)}
</ol>
</>
);
Expand Down
4 changes: 4 additions & 0 deletions src/firmware/bootloaderInstructions/assets/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Files in this folder are licensed under the Creative Commons Attribution-
NonCommercial-ShareAlike 4.0 International License. To view a copy of this
license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a
letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
WEBVTT
disconnect-io
00:00.100 --> 00:08.000

hold-button
00:08.000 --> 00:14.000

wait-for-light
00:14.000 --> 00:21.000

keep-holding
00:21.000 --> 00:25.000
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
WEBVTT
hold-button
00:00.100 --> 00:05.000

wait-for-light
00:05.000 --> 00:09.000

connect-usb
00:09.000 --> 00:15.000

release-button
00:15.000 --> 00:21.000
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
WEBVTT
hold-button
00:00.100 --> 00:06.000

connect-usb
00:06.000 --> 00:13.000

wait-for-light
00:13.000 --> 00:20.000

release-button
00:20.000 --> 00:24.000
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
WEBVTT
hold-button
00:00.100 --> 00:05.000

wait-for-light
00:05.000 --> 00:15.000

keep-holding
00:15.000 --> 00:20.000
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
WEBVTT
hold-button
00:00.100 --> 00:07.000

connect-usb
00:07.000 --> 00:13.000

wait-for-light
00:13.000 --> 00:19.000

release-button
00:19.000 --> 00:24.000
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
WEBVTT
hold-button
00:00.100 --> 00:07.000

wait-for-light
00:07.000 --> 00:16.000

keep-holding
00:16.000 --> 00:20.000
12 changes: 12 additions & 0 deletions src/firmware/bootloaderInstructions/bootloaderInstructions.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors

.pb-active-step {
font-weight: bolder;
font-size: larger;
}

.pb-bootloader-video {
width: 90%;
align-self: center;
}
8 changes: 5 additions & 3 deletions src/firmware/bootloaderInstructions/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"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:",
"instruction": "To flash the firmware, the hub must be placed in bootloader mode. {startPoweredOff}, then follow the instructions below:",
"startPoweredOff": {
"default": "Start with the hub powered off",
"usb": "Start with the hub powered off and the USB cable disconnected"
},
"button": {
"bluetooth": "Bluetooth button",
"power": "power button"
Expand All @@ -18,8 +22,6 @@
"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.",
Expand Down
9 changes: 9 additions & 0 deletions src/react-app-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ declare module '*.zip' {
export default src;
}

declare module '*.mp4' {
const src: string;
export default src;
}

declare module '*.vtt' {
const src: string;
export default src;
}
// https://webpack.js.org/api/hot-module-replacement/
interface NodeModule {
hot?: { dispose: (callback: (data) => void) => void };
Expand Down

0 comments on commit 0f871c6

Please sign in to comment.