Skip to content

Commit 0f871c6

Browse files
committed
bootloaderInstructions: add videos
This adds videos to show how to enter bootloader mode. Issue: pybricks/support#728
1 parent be4a18b commit 0f871c6

18 files changed

Lines changed: 256 additions & 20 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
## [Unreleased]
66

7+
### Added
8+
- Added videos to firmware flash dialogs ([support#728]).
9+
710
### Changed
811
- Moved documentation show/hide button from settings to editor area ([support#778]).
912

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

1619
[support#438]: https://github.com/pybricks/support/issues/438
20+
[support#728]: https://github.com/pybricks/support/issues/728
1721
[support#778]: https://github.com/pybricks/support/issues/778
1822
[support#807]: https://github.com/pybricks/support/issues/807
1923
[support#823]: https://github.com/pybricks/support/issues/823

src/firmware/bootloaderInstructions/BootloaderInstructions.tsx

Lines changed: 150 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,53 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright (c) 2022 The Pybricks Authors
33

4+
import './bootloaderInstructions.scss';
45
import { Callout, Intent } from '@blueprintjs/core';
5-
import React, { useMemo } from 'react';
6+
import classNames from 'classnames';
7+
import React, { useEffect, useMemo, useRef, useState } from 'react';
68
import {
79
pybricksUsbDfuWindowsDriverInstallUrl,
810
pybricksUsbLinuxUdevRulesUrl,
911
} from '../../app/constants';
1012
import ExternalLinkIcon from '../../components/ExternalLinkIcon';
1113
import { Hub, hubHasBluetoothButton, hubHasUSB } from '../../components/hubPicker';
1214
import { isLinux, isWindows } from '../../utils/os';
15+
import cityHubMp4 from './assets/bootloader-cityhub-540.mp4';
16+
import cityHubVtt from './assets/bootloader-cityhub-metadata.vtt';
17+
import essentialHubMp4 from './assets/bootloader-essentialhub-540.mp4';
18+
import essentialHubVtt from './assets/bootloader-essentialhub-metadata.vtt';
19+
import inventorHubMp4 from './assets/bootloader-inventorhub-540.mp4';
20+
import inventorHubVtt from './assets/bootloader-inventorhub-metadata.vtt';
21+
import moveHubMp4 from './assets/bootloader-movehub-540.mp4';
22+
import moveHubVtt from './assets/bootloader-movehub-metadata.vtt';
23+
import primeHubMp4 from './assets/bootloader-primehub-540.mp4';
24+
import primeHubVtt from './assets/bootloader-primehub-metadata.vtt';
25+
import technicHubMp4 from './assets/bootloader-technichub-540.mp4';
26+
import technicHubVtt from './assets/bootloader-technichub-metadata.vtt';
1327
import { useI18n } from './i18n';
1428

1529
type BootloaderInstructionsProps = {
1630
hubType: Hub;
1731
};
1832

33+
const videoFileMap: ReadonlyMap<Hub, string> = new Map([
34+
[Hub.City, cityHubMp4],
35+
[Hub.Essential, essentialHubMp4],
36+
[Hub.Inventor, inventorHubMp4],
37+
[Hub.Move, moveHubMp4],
38+
[Hub.Prime, primeHubMp4],
39+
[Hub.Technic, technicHubMp4],
40+
]);
41+
42+
const metadataFileMap: ReadonlyMap<Hub, string> = new Map([
43+
[Hub.City, cityHubVtt],
44+
[Hub.Essential, essentialHubVtt],
45+
[Hub.Inventor, inventorHubVtt],
46+
[Hub.Move, moveHubVtt],
47+
[Hub.Prime, primeHubVtt],
48+
[Hub.Technic, technicHubVtt],
49+
]);
50+
1951
/**
2052
* Provides customized instructions on how to enter bootloader mode based
2153
* on the hub type.
@@ -41,6 +73,36 @@ const BootloaderInstructions: React.VoidFunctionComponent<
4173
};
4274
}, [i18n, hubType]);
4375

76+
const metadataTrackRef = useRef<HTMLTrackElement>(null);
77+
const [activeStep, setActiveStep] = useState('');
78+
79+
useEffect(() => {
80+
const element = metadataTrackRef.current;
81+
82+
// istanbul ignore if: should not happen
83+
if (element === null) {
84+
return;
85+
}
86+
87+
// istanbul ignore else: jsdom doesn't support video
88+
if (process.env.NODE_ENV === 'test') {
89+
return;
90+
}
91+
92+
const handleCueChange = (e: Event) => {
93+
const track = e.target as TextTrack;
94+
setActiveStep(track.activeCues?.[0]?.id ?? '');
95+
};
96+
97+
element.track.addEventListener('cuechange', handleCueChange);
98+
element.track.mode = 'hidden';
99+
100+
return () => {
101+
element.track.removeEventListener('cuechange', handleCueChange);
102+
element.track.mode = 'disabled';
103+
};
104+
}, [setActiveStep]);
105+
44106
return (
45107
<>
46108
{hubHasUSB(hubType) && isLinux() && (
@@ -69,36 +131,107 @@ const BootloaderInstructions: React.VoidFunctionComponent<
69131
<ExternalLinkIcon />
70132
</Callout>
71133
)}
72-
<p>{i18n.translate('instruction')}</p>
73-
<ol>
74-
{hubHasUSB(hubType) && <li>{i18n.translate('step.disconnectUsb')}</li>}
75134

76-
<li>{i18n.translate('step.powerOff')}</li>
135+
<video
136+
controls
137+
controlsList="nodownload nofullscreen"
138+
muted
139+
disablePictureInPicture
140+
className="pb-bootloader-video"
141+
>
142+
<source src={videoFileMap.get(hubType)} type="video/mp4" />
143+
<track
144+
kind="metadata"
145+
src={metadataFileMap.get(hubType)}
146+
ref={metadataTrackRef}
147+
/>
148+
</video>
149+
150+
<div className="pb-spacer" />
77151

152+
<p>
153+
{i18n.translate('instruction', {
154+
startPoweredOff: hubHasUSB(hubType)
155+
? i18n.translate('startPoweredOff.usb')
156+
: i18n.translate('startPoweredOff.default'),
157+
})}
158+
</p>
159+
<ol>
78160
{/* City hub has power issues and requires disconnecting motors/sensors */}
79-
{hubType === Hub.City && <li>{i18n.translate('step.disconnectIo')}</li>}
161+
{hubType === Hub.City && (
162+
<li
163+
className={classNames(
164+
activeStep === 'disconnect-io' && 'pb-active-step',
165+
)}
166+
>
167+
{i18n.translate('step.disconnectIo')}
168+
</li>
169+
)}
80170

81-
<li>{i18n.translate('step.holdButton', { button })}</li>
171+
<li
172+
className={classNames(
173+
activeStep === 'hold-button' && 'pb-active-step',
174+
)}
175+
>
176+
{i18n.translate('step.holdButton', { button })}
177+
</li>
82178

83-
{hubHasUSB(hubType) && <li>{i18n.translate('step.connectUsb')}</li>}
179+
{/* not strictly necessary, but order is swapped in the video,
180+
so we match it here. */}
181+
{hubType !== Hub.Essential && hubHasUSB(hubType) && (
182+
<li
183+
className={classNames(
184+
activeStep === 'connect-usb' && 'pb-active-step',
185+
)}
186+
>
187+
{i18n.translate('step.connectUsb')}
188+
</li>
189+
)}
84190

85-
<li>
191+
<li
192+
className={classNames(
193+
activeStep === 'wait-for-light' && 'pb-active-step',
194+
)}
195+
>
86196
{i18n.translate('step.waitForLight', {
87197
button,
88198
light,
89199
lightPattern,
90200
})}
91201
</li>
92202

93-
<li>
94-
{i18n.translate(
95-
/* hubs with USB will keep the power on, but other hubs won't */
96-
hubHasUSB(hubType) ? 'step.releaseButton' : 'step.keepHolding',
97-
{
203+
{hubType === Hub.Essential && hubHasUSB(hubType) && (
204+
<li
205+
className={classNames(
206+
activeStep === 'connect-usb' && 'pb-active-step',
207+
)}
208+
>
209+
{i18n.translate('step.connectUsb')}
210+
</li>
211+
)}
212+
213+
{/* hubs with USB will keep the power on, but other hubs won't */}
214+
{hubHasUSB(hubType) ? (
215+
<li
216+
className={classNames(
217+
activeStep === 'release-button' && 'pb-active-step',
218+
)}
219+
>
220+
{i18n.translate('step.releaseButton', {
98221
button,
99-
},
100-
)}
101-
</li>
222+
})}
223+
</li>
224+
) : (
225+
<li
226+
className={classNames(
227+
activeStep === 'keep-holding' && 'pb-active-step',
228+
)}
229+
>
230+
{i18n.translate('step.keepHolding', {
231+
button,
232+
})}
233+
</li>
234+
)}
102235
</ol>
103236
</>
104237
);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Files in this folder are licensed under the Creative Commons Attribution-
2+
NonCommercial-ShareAlike 4.0 International License. To view a copy of this
3+
license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a
4+
letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
WEBVTT
2+
3+
disconnect-io
4+
00:00.100 --> 00:08.000
5+
6+
hold-button
7+
00:08.000 --> 00:14.000
8+
9+
wait-for-light
10+
00:14.000 --> 00:21.000
11+
12+
keep-holding
13+
00:21.000 --> 00:25.000
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
WEBVTT
2+
3+
hold-button
4+
00:00.100 --> 00:05.000
5+
6+
wait-for-light
7+
00:05.000 --> 00:09.000
8+
9+
connect-usb
10+
00:09.000 --> 00:15.000
11+
12+
release-button
13+
00:15.000 --> 00:21.000
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
WEBVTT
2+
3+
hold-button
4+
00:00.100 --> 00:06.000
5+
6+
connect-usb
7+
00:06.000 --> 00:13.000
8+
9+
wait-for-light
10+
00:13.000 --> 00:20.000
11+
12+
release-button
13+
00:20.000 --> 00:24.000
Binary file not shown.

0 commit comments

Comments
 (0)