mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-14 10:35:11 +00:00
Merge pull request #1009 from pybricks/dlech
restore official firmware dialog
This commit is contained in:
+13
-1
@@ -7,12 +7,24 @@
|
||||
### Added
|
||||
- Added better error message when no files to backup ([support#681]).
|
||||
- Added multi-step firmware flashing dialog.
|
||||
- Added support for flashing firmware via USB DFU.
|
||||
- Added support for flashing firmware via USB DFU ([support#659]).
|
||||
- Added an interactive introductory tour of the app.
|
||||
- Added restore official LEGO firmware dialog.
|
||||
|
||||
### Changed
|
||||
- Updated dependencies.
|
||||
- Updated firmware to Pybricks v3.2.0b3:
|
||||
|
||||
### Fixed
|
||||
- Fix integral control not working properly.
|
||||
|
||||
### Changed
|
||||
- `Motor.run_time` no longer raises an exception for negative time values.
|
||||
|
||||
### Fixed
|
||||
- Fixed deleting files that are not open in the editor.
|
||||
|
||||
[support#659]: https://github.com/pybricks/support/issues/659
|
||||
[support#681]: https://github.com/pybricks/support/issues/681
|
||||
|
||||
## [2.0.0-beta.3] - 2022-07-06
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import './UnexpectedErrorAlert.scss';
|
||||
import { AnchorButton, Button, ButtonGroup, Collapse, Intent } from '@blueprintjs/core';
|
||||
import {
|
||||
AnchorButton,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Collapse,
|
||||
Intent,
|
||||
Pre,
|
||||
} from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
import { useId } from 'react-aria';
|
||||
import { CreateToast } from '../i18nToaster';
|
||||
@@ -33,7 +40,7 @@ const UnexpectedErrorAlert: React.VoidFunctionComponent<UnexpectedErrorAlertProp
|
||||
<span id={labelId}>{i18n.translate('technicalInfo')}</span>
|
||||
</span>
|
||||
<Collapse isOpen={isExpanded}>
|
||||
<pre className="pb-alerts-stack-trace">{error.stack}</pre>
|
||||
<Pre className="pb-alerts-stack-trace">{error.stack}</Pre>
|
||||
</Collapse>
|
||||
<div>
|
||||
<ButtonGroup minimal={true} fill={true}>
|
||||
|
||||
@@ -44,6 +44,11 @@ export const pybricksUsbDfuWindowsDriverInstallUrl =
|
||||
export const pybricksUsbLinuxUdevRulesUrl =
|
||||
'https://github.com/pybricks/support/discussions/688#discussioncomment-3239099';
|
||||
|
||||
export const pybricksBleFirmwareRestoreVideoUrl =
|
||||
'https://pybricks.com/install/technic-boost-city/#restoring-the-original-firmware';
|
||||
|
||||
export const pybricksDfuRestoreUrl = 'https://dfu.pybricks.com';
|
||||
|
||||
/** Pybricks copyright statement. */
|
||||
export const pybricksCopyright = 'Copyright (c) 2020-2022 The Pybricks Authors';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import { Button, Code, Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { isIOS, isLinux } from '../../utils/os';
|
||||
@@ -19,9 +19,9 @@ const NoWebBluetooth: React.VoidFunctionComponent = () => {
|
||||
<>
|
||||
<p>{i18n.translate('noWebBluetooth.linux')}</p>
|
||||
<p>
|
||||
<code>
|
||||
<Code>
|
||||
chrome://flags/#enable-experimental-web-platform-features
|
||||
</code>
|
||||
</Code>
|
||||
<Button
|
||||
icon="duplicate"
|
||||
small={true}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { Classes, FormGroup, InputGroup, Intent, Tag } from '@blueprintjs/core';
|
||||
import { Classes, Code, FormGroup, InputGroup, Intent, Tag } from '@blueprintjs/core';
|
||||
import type { AriaButtonProps } from '@react-types/button';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { useButton } from 'react-aria';
|
||||
@@ -108,8 +108,8 @@ const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
|
||||
return (
|
||||
<>
|
||||
{i18n.translate('helpText.hasInvalidFirstCharacter', {
|
||||
letters: <code className={Classes.CODE}>a…z</code>,
|
||||
underscore: <code className={Classes.CODE}>_</code>,
|
||||
letters: <Code className={Classes.CODE}>a…z</Code>,
|
||||
underscore: <Code className={Classes.CODE}>_</Code>,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
@@ -117,10 +117,10 @@ const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
|
||||
return (
|
||||
<>
|
||||
{i18n.translate('helpText.hasInvalidCharacters', {
|
||||
letters: <code className={Classes.CODE}>a…z</code>,
|
||||
numbers: <code className={Classes.CODE}>0…9</code>,
|
||||
dash: <code className={Classes.CODE}>-</code>,
|
||||
underscore: <code className={Classes.CODE}>_</code>,
|
||||
letters: <Code className={Classes.CODE}>a…z</Code>,
|
||||
numbers: <Code className={Classes.CODE}>0…9</Code>,
|
||||
dash: <Code className={Classes.CODE}>-</Code>,
|
||||
underscore: <Code className={Classes.CODE}>_</Code>,
|
||||
})}{' '}
|
||||
<FixItButton onPress={handleHasInvalidCharacters} />
|
||||
</>
|
||||
|
||||
@@ -395,24 +395,3 @@ export const firmwareDidInstallPybricks = createAction(() => ({
|
||||
export const firmwareDidFailToInstallPybricks = createAction(() => ({
|
||||
type: 'firmware.action.didFailToInstallPybricks',
|
||||
}));
|
||||
|
||||
/**
|
||||
* Action that triggers the restore LEGO firmware saga.
|
||||
*/
|
||||
export const firmwareRestoreLego = createAction(() => ({
|
||||
type: 'firmware.action.restoreLego',
|
||||
}));
|
||||
|
||||
/**
|
||||
* Action that indicates {@link firmwareRestoreLego} succeeded.
|
||||
*/
|
||||
export const firmwareDidRestoreLego = createAction(() => ({
|
||||
type: 'firmware.action.didRestoreLego',
|
||||
}));
|
||||
|
||||
/**
|
||||
* Action that indicates {@link firmwareRestoreLego} failed.
|
||||
*/
|
||||
export const firmwareDidFailToRestoreLego = createAction(() => ({
|
||||
type: 'firmware.action.didFailToRestoreLego',
|
||||
}));
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Callout,
|
||||
Checkbox,
|
||||
Classes,
|
||||
Code,
|
||||
ControlGroup,
|
||||
DialogStep,
|
||||
FormGroup,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
MenuItem,
|
||||
MultistepDialog,
|
||||
NonIdealState,
|
||||
Pre,
|
||||
Spinner,
|
||||
Switch,
|
||||
} from '@blueprintjs/core';
|
||||
@@ -69,12 +71,12 @@ const SelectHubPanel: React.VoidFunctionComponent = () => {
|
||||
popoverClassName={Classes2.POPOVER2_CONTENT_SIZING}
|
||||
placement="right-end"
|
||||
content={
|
||||
<div>
|
||||
<h3>
|
||||
<div className={Classes.RUNNING_TEXT}>
|
||||
<h4>
|
||||
{i18n.translate(
|
||||
'selectHubPanel.notOnListButton.info.mindstorms.title',
|
||||
)}
|
||||
</h3>
|
||||
</h4>
|
||||
<ul>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
@@ -92,11 +94,11 @@ const SelectHubPanel: React.VoidFunctionComponent = () => {
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<h3>
|
||||
<h4>
|
||||
{i18n.translate(
|
||||
'selectHubPanel.notOnListButton.info.poweredUp.title',
|
||||
)}
|
||||
</h3>
|
||||
</h4>
|
||||
<ul>
|
||||
<li>
|
||||
{i18n.translate(
|
||||
@@ -154,28 +156,27 @@ const AcceptLicensePanel: React.VoidFunctionComponent<AcceptLicensePanelProps> =
|
||||
|
||||
return (
|
||||
<div className={dialogBody}>
|
||||
<div className="pb-firmware-installPybricksDialog-license">
|
||||
<div className="pb-firmware-installPybricksDialog-license-text">
|
||||
{data ? (
|
||||
<pre>{data.licenseText}</pre>
|
||||
) : (
|
||||
<NonIdealState
|
||||
icon={error ? 'error' : <Spinner />}
|
||||
description={
|
||||
error
|
||||
? i18n.translate('licensePanel.licenseText.error')
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Checkbox
|
||||
label={i18n.translate('licensePanel.acceptCheckbox.label')}
|
||||
checked={licenseAccepted}
|
||||
onChange={(e) => onLicenseAcceptedChanged(e.currentTarget.checked)}
|
||||
disabled={!data}
|
||||
/>
|
||||
<div className="pb-firmware-installPybricksDialog-license-text">
|
||||
{data ? (
|
||||
<Pre>{data.licenseText}</Pre>
|
||||
) : (
|
||||
<NonIdealState
|
||||
icon={error ? 'error' : <Spinner />}
|
||||
description={
|
||||
error
|
||||
? i18n.translate('licensePanel.licenseText.error')
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Checkbox
|
||||
className="pb-firmware-installPybricksDialog-license-checkbox"
|
||||
label={i18n.translate('licensePanel.acceptCheckbox.label')}
|
||||
checked={licenseAccepted}
|
||||
onChange={(e) => onLicenseAcceptedChanged(e.currentTarget.checked)}
|
||||
disabled={!data}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -248,7 +249,7 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
|
||||
<Switch
|
||||
labelElement={i18n.translate(
|
||||
'optionsPanel.customMain.include.label',
|
||||
{ main: <code>main.py</code> },
|
||||
{ main: <Code>main.py</Code> },
|
||||
)}
|
||||
checked={includeProgram}
|
||||
onChange={(e) =>
|
||||
@@ -349,82 +350,84 @@ const BootloaderModePanel: React.VoidFunctionComponent<BootloaderModePanelProps>
|
||||
return (
|
||||
<div className={dialogBody}>
|
||||
{hubHasUSB(hubType) && isLinux() && (
|
||||
<p>
|
||||
<Callout intent={Intent.WARNING} icon="warning-sign">
|
||||
{i18n.translate('bootloaderPanel.warning.linux')}{' '}
|
||||
<a
|
||||
href={pybricksUsbLinuxUdevRulesUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{i18n.translate('bootloaderPanel.warning.learnMore')}
|
||||
</a>
|
||||
<ExternalLinkIcon />
|
||||
</Callout>
|
||||
</p>
|
||||
<Callout intent={Intent.WARNING} icon="warning-sign">
|
||||
{i18n.translate('bootloaderPanel.warning.linux')}{' '}
|
||||
<a
|
||||
href={pybricksUsbLinuxUdevRulesUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{i18n.translate('bootloaderPanel.warning.learnMore')}
|
||||
</a>
|
||||
<ExternalLinkIcon />
|
||||
</Callout>
|
||||
)}
|
||||
{hubHasUSB(hubType) && isWindows() && (
|
||||
<p>
|
||||
<Callout intent={Intent.WARNING} icon="warning-sign">
|
||||
{i18n.translate('bootloaderPanel.warning.windows')}{' '}
|
||||
<a
|
||||
href={pybricksUsbDfuWindowsDriverInstallUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{i18n.translate('bootloaderPanel.warning.learnMore')}
|
||||
</a>
|
||||
<ExternalLinkIcon />
|
||||
</Callout>
|
||||
</p>
|
||||
<Callout intent={Intent.WARNING} icon="warning-sign">
|
||||
{i18n.translate('bootloaderPanel.warning.windows')}{' '}
|
||||
<a
|
||||
href={pybricksUsbDfuWindowsDriverInstallUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{i18n.translate('bootloaderPanel.warning.learnMore')}
|
||||
</a>
|
||||
<ExternalLinkIcon />
|
||||
</Callout>
|
||||
)}
|
||||
|
||||
<p>{i18n.translate('bootloaderPanel.instruction1')}</p>
|
||||
<ol>
|
||||
{hubHasUSB(hubType) && (
|
||||
<li>{i18n.translate('bootloaderPanel.step.disconnectUsb')}</li>
|
||||
)}
|
||||
|
||||
<li>{i18n.translate('bootloaderPanel.step.powerOff')}</li>
|
||||
|
||||
{/* City hub has power issues and requires disconnecting motors/sensors */}
|
||||
{hubType === Hub.City && (
|
||||
<li>{i18n.translate('bootloaderPanel.step.disconnectIo')}</li>
|
||||
)}
|
||||
|
||||
<li>{i18n.translate('bootloaderPanel.step.holdButton', { button })}</li>
|
||||
|
||||
{hubHasUSB(hubType) && (
|
||||
<li>{i18n.translate('bootloaderPanel.step.connectUsb')}</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
{i18n.translate('bootloaderPanel.step.waitForLight', {
|
||||
button,
|
||||
light,
|
||||
lightPattern,
|
||||
})}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{i18n.translate(
|
||||
/* hubs with USB will keep the power on, but other hubs won't */
|
||||
hubHasUSB(hubType)
|
||||
? 'bootloaderPanel.step.releaseButton'
|
||||
: 'bootloaderPanel.step.keepHolding',
|
||||
{
|
||||
button,
|
||||
},
|
||||
<div className={Classes.RUNNING_TEXT}>
|
||||
<p>{i18n.translate('bootloaderPanel.instruction1')}</p>
|
||||
<ol>
|
||||
{hubHasUSB(hubType) && (
|
||||
<li>{i18n.translate('bootloaderPanel.step.disconnectUsb')}</li>
|
||||
)}
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
{i18n.translate('bootloaderPanel.instruction2', {
|
||||
flashFirmware: (
|
||||
<strong>{i18n.translate('flashFirmwareButton.label')}</strong>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
|
||||
<li>{i18n.translate('bootloaderPanel.step.powerOff')}</li>
|
||||
|
||||
{/* City hub has power issues and requires disconnecting motors/sensors */}
|
||||
{hubType === Hub.City && (
|
||||
<li>{i18n.translate('bootloaderPanel.step.disconnectIo')}</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
{i18n.translate('bootloaderPanel.step.holdButton', { button })}
|
||||
</li>
|
||||
|
||||
{hubHasUSB(hubType) && (
|
||||
<li>{i18n.translate('bootloaderPanel.step.connectUsb')}</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
{i18n.translate('bootloaderPanel.step.waitForLight', {
|
||||
button,
|
||||
light,
|
||||
lightPattern,
|
||||
})}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{i18n.translate(
|
||||
/* hubs with USB will keep the power on, but other hubs won't */
|
||||
hubHasUSB(hubType)
|
||||
? 'bootloaderPanel.step.releaseButton'
|
||||
: 'bootloaderPanel.step.keepHolding',
|
||||
{
|
||||
button,
|
||||
},
|
||||
)}
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
{i18n.translate('bootloaderPanel.instruction2', {
|
||||
flashFirmware: (
|
||||
<strong>
|
||||
{i18n.translate('flashFirmwareButton.label')}
|
||||
</strong>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,24 +5,34 @@
|
||||
|
||||
.pb-firmware-installPybricksDialog {
|
||||
&-body {
|
||||
min-height: bp.$pt-grid-size * 35;
|
||||
min-height: bp.$pt-grid-size * 40;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: bp.$pt-grid-size;
|
||||
}
|
||||
|
||||
&-license {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: bp.$pt-grid-size;
|
||||
min-height: inherit;
|
||||
|
||||
&-text {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-height: bp.$pt-grid-size * 30;
|
||||
max-height: bp.$pt-grid-size * 35;
|
||||
overflow: auto;
|
||||
|
||||
& .#{bp.$ns}-non-ideal-state {
|
||||
min-height: bp.$pt-grid-size * 30;
|
||||
min-height: bp.$pt-grid-size * 35;
|
||||
}
|
||||
|
||||
& pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-checkbox {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ test('initial state', () => {
|
||||
"isOpen": false,
|
||||
},
|
||||
"progress": null,
|
||||
"restoreOfficialDialog": Object {
|
||||
"isOpen": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { Reducer, combineReducers } from 'redux';
|
||||
import { didFailToFinish, didFinish, didProgress, didStart } from './actions';
|
||||
import installPybricksDialog from './installPybricksDialog/reducers';
|
||||
import restoreOfficialDialog from './restoreOfficialDialog/reducers';
|
||||
|
||||
const flashing: Reducer<boolean> = (state = false, action) => {
|
||||
if (didStart.matches(action)) {
|
||||
@@ -29,4 +30,9 @@ const progress: Reducer<number | null> = (state = null, action) => {
|
||||
return state;
|
||||
};
|
||||
|
||||
export default combineReducers({ installPybricksDialog, flashing, progress });
|
||||
export default combineReducers({
|
||||
installPybricksDialog,
|
||||
restoreOfficialDialog,
|
||||
flashing,
|
||||
progress,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { Classes, Dialog } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
pybricksBleFirmwareRestoreVideoUrl,
|
||||
pybricksDfuRestoreUrl,
|
||||
} from '../../app/constants';
|
||||
import { useSelector } from '../../reducers';
|
||||
import ExternalLinkIcon from '../../utils/ExternalLinkIcon';
|
||||
import { firmwareRestoreOfficialDialogHide } from './actions';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const RestoreOfficialDialog: React.VoidFunctionComponent = () => {
|
||||
const { isOpen } = useSelector((s) => s.firmware.restoreOfficialDialog);
|
||||
const dispatch = useDispatch();
|
||||
const i18n = useI18n();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isOpen}
|
||||
title={i18n.translate('title')}
|
||||
onClose={() => dispatch(firmwareRestoreOfficialDialogHide())}
|
||||
>
|
||||
<div className={classNames(Classes.DIALOG_BODY, Classes.RUNNING_TEXT)}>
|
||||
<h4>{i18n.translate('poweredUpHubs.title')}</h4>
|
||||
<p>{i18n.translate('poweredUpHubs.message')}</p>
|
||||
<p>
|
||||
<a
|
||||
href={pybricksBleFirmwareRestoreVideoUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{i18n.translate('poweredUpHubs.action')}
|
||||
</a>
|
||||
<ExternalLinkIcon />
|
||||
</p>
|
||||
<h4>{i18n.translate('spikeHubs.title')}</h4>
|
||||
<p>{i18n.translate('spikeHubs.message')}</p>
|
||||
<p>
|
||||
<a href={pybricksDfuRestoreUrl} target="_blank" rel="noreferrer">
|
||||
{i18n.translate('spikeHubs.action')}
|
||||
</a>
|
||||
<ExternalLinkIcon />
|
||||
</p>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default RestoreOfficialDialog;
|
||||
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
export {
|
||||
show as firmwareRestoreOfficialDialogShow,
|
||||
hide as firmwareRestoreOfficialDialogHide,
|
||||
} from './redux';
|
||||
@@ -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<typeof translations> {
|
||||
// istanbul ignore next: babel-loader rewrites this line
|
||||
const [i18n] = useShopifyI18n();
|
||||
return i18n;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import reducer from './redux';
|
||||
|
||||
export default reducer;
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
type State = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
const initialState: State = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'restoreOfficialDialog',
|
||||
initialState,
|
||||
reducers: {
|
||||
show(state) {
|
||||
state.isOpen = true;
|
||||
},
|
||||
hide(state) {
|
||||
state.isOpen = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { show, hide } = slice.actions;
|
||||
export default slice.reducer;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "Restore official LEGO® Firmware",
|
||||
"poweredUpHubs": {
|
||||
"title": "Powered Up Hubs",
|
||||
"message": "The official firmware can be restored on Powered Up hubs by putting the hub in bootloader mode and connecting to the hub using one of the official LEGO apps.",
|
||||
"action": "Watch video."
|
||||
},
|
||||
"spikeHubs": {
|
||||
"title": "SPIKE/MINDSTORMS Hubs",
|
||||
"message": "The official LEGO software for these hubs does not have a way to restore the firmware on these hubs. So, we have provided a special site to do this for you.",
|
||||
"action": "Pybricks DFU restore tool."
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,16 @@ a.#{bp.$ns}-button {
|
||||
min-width: math.div(bp.$pt-grid-size, 2);
|
||||
}
|
||||
|
||||
.#{bp.$ns}-running-text {
|
||||
*:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
*:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// make scrollbars fit our style
|
||||
::-webkit-scrollbar {
|
||||
width: 16px;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Classes,
|
||||
Dialog,
|
||||
NonIdealState,
|
||||
Pre,
|
||||
Spinner,
|
||||
Text,
|
||||
} from '@blueprintjs/core';
|
||||
@@ -190,7 +191,7 @@ const LicenseInfoPanel = React.forwardRef<HTMLDivElement, LicenseInfoPanelProps>
|
||||
{i18n.translate('help.selectPackage')}
|
||||
</NonIdealState>
|
||||
) : (
|
||||
<div>
|
||||
<>
|
||||
<Card>
|
||||
<p>
|
||||
<strong>{i18n.translate('packageLabel')}</strong>{' '}
|
||||
@@ -211,9 +212,9 @@ const LicenseInfoPanel = React.forwardRef<HTMLDivElement, LicenseInfoPanelProps>
|
||||
</p>
|
||||
</Card>
|
||||
<div className="pb-license-text">
|
||||
<pre>{licenseInfo.licenseText}</pre>
|
||||
<Pre>{licenseInfo.licenseText}</Pre>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -50,9 +50,19 @@
|
||||
.pb-license-info {
|
||||
width: 80%;
|
||||
padding: 0px 16px;
|
||||
overflow: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: bp.$pt-grid-size;
|
||||
}
|
||||
|
||||
.pb-license-text {
|
||||
padding: 0px 16px;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
|
||||
& pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import { cleanup, getByLabelText, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../test';
|
||||
import { firmwareInstallPybricks, firmwareRestoreLego } from '../firmware/actions';
|
||||
import { firmwareInstallPybricks } from '../firmware/actions';
|
||||
import { firmwareRestoreOfficialDialogShow } from '../firmware/restoreOfficialDialog/actions';
|
||||
import Settings from './Settings';
|
||||
|
||||
afterEach(() => {
|
||||
@@ -62,7 +63,7 @@ describe('firmware', () => {
|
||||
});
|
||||
await user.click(button);
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith(firmwareRestoreLego());
|
||||
expect(dispatch).toHaveBeenCalledWith(firmwareRestoreOfficialDialogShow());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ import {
|
||||
} from '../app/constants';
|
||||
import { Button } from '../components/Button';
|
||||
import HelpButton from '../components/HelpButton';
|
||||
import { firmwareInstallPybricks, firmwareRestoreLego } from '../firmware/actions';
|
||||
import { firmwareInstallPybricks } from '../firmware/actions';
|
||||
import { InstallPybricksDialog } from '../firmware/installPybricksDialog/InstallPybricksDialog';
|
||||
import RestoreOfficialDialog from '../firmware/restoreOfficialDialog/RestoreOfficialDialog';
|
||||
import { firmwareRestoreOfficialDialogShow } from '../firmware/restoreOfficialDialog/actions';
|
||||
import { pseudolocalize } from '../i18n';
|
||||
import { useSelector } from '../reducers';
|
||||
import ExternalLinkIcon from '../utils/ExternalLinkIcon';
|
||||
@@ -108,8 +110,9 @@ const Settings: React.VoidFunctionComponent = () => {
|
||||
minimal={true}
|
||||
icon="download"
|
||||
label={i18n.translate('firmware.flashLegoButton.label')}
|
||||
onPress={() => dispatch(firmwareRestoreLego())}
|
||||
onPress={() => dispatch(firmwareRestoreOfficialDialogShow())}
|
||||
/>
|
||||
<RestoreOfficialDialog />
|
||||
</FormGroup>
|
||||
<FormGroup label={i18n.translate('help.title')}>
|
||||
<ButtonGroup minimal={true} vertical={true} alignText="left">
|
||||
|
||||
Reference in New Issue
Block a user