mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
move progress from status bar to firmware button
This commit is contained in:
@@ -1,20 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { Action } from 'redux';
|
||||
import { assert } from '../utils';
|
||||
|
||||
/**
|
||||
* High-level bootloader actions.
|
||||
*/
|
||||
export enum FlashFirmwareActionType {
|
||||
/**
|
||||
* Flash new firmware to the device.
|
||||
*/
|
||||
/** Request to flash new firmware to the device. */
|
||||
FlashFirmware = 'flashFirmware.action.flashFirmware',
|
||||
/**
|
||||
* Firmware flash progress.
|
||||
*/
|
||||
Progress = 'flashFirmware.action.progress',
|
||||
/** Flashing started. */
|
||||
DidStart = 'flashFirmware.action.didStart',
|
||||
/** Firmware flash progress. */
|
||||
DidProgress = 'flashFirmware.action.didProgress',
|
||||
/** Flashing finished successfully. */
|
||||
DidFinish = 'flashFirmware.action.didFinish',
|
||||
/** Flashing firmware failed. */
|
||||
DidFailToFinish = 'flashFirmware.action.didFailToFinish',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,19 +36,46 @@ export function flashFirmware(data?: ArrayBuffer): FlashFirmwareFlashAction {
|
||||
return { type: FlashFirmwareActionType.FlashFirmware, data };
|
||||
}
|
||||
|
||||
export type FlashFirmwareProgressAction = Action<FlashFirmwareActionType.Progress> & {
|
||||
/**
|
||||
* The number of bytes that have been flashed so far.
|
||||
*/
|
||||
complete: number;
|
||||
/**
|
||||
* The total number of bytes to be flashed.
|
||||
*/
|
||||
total: number;
|
||||
/** Action that indicates flashing firmware started. */
|
||||
export type FlashFirmwareDidStartAction = Action<FlashFirmwareActionType.DidStart>;
|
||||
|
||||
/**
|
||||
* Action that indicates flashing firmware started.
|
||||
* @param total The total number of bytes to be flashed.
|
||||
*/
|
||||
export function didStart(): FlashFirmwareDidStartAction {
|
||||
return { type: FlashFirmwareActionType.DidStart };
|
||||
}
|
||||
|
||||
/** Action that indicates current firmware flashing progress. */
|
||||
export type FlashFirmwareDidProgressAction = Action<FlashFirmwareActionType.DidProgress> & {
|
||||
/** The current progress (0 to 1). */
|
||||
value: number;
|
||||
};
|
||||
|
||||
export function progress(complete: number, total: number): FlashFirmwareProgressAction {
|
||||
return { type: FlashFirmwareActionType.Progress, complete, total };
|
||||
/**
|
||||
* Action that indicates current firmware flashing progress.
|
||||
* @param value The current progress (0 to 1).
|
||||
*/
|
||||
export function didProgress(value: number): FlashFirmwareDidProgressAction {
|
||||
assert(value >= 0 && value <= 1, 'value out of range');
|
||||
return { type: FlashFirmwareActionType.DidProgress, value };
|
||||
}
|
||||
|
||||
/** Action that indicates that flashing firmware completed successfully. */
|
||||
export type FlashFirmwareDidFinishAction = Action<FlashFirmwareActionType.DidFinish>;
|
||||
|
||||
/** Action that indicates that flashing firmware completed successfully. */
|
||||
export function didFinish(): FlashFirmwareDidFinishAction {
|
||||
return { type: FlashFirmwareActionType.DidFinish };
|
||||
}
|
||||
|
||||
/** Action that indicates that flashing failed. */
|
||||
export type FlashFirmwareDidFailToFinishAction = Action<FlashFirmwareActionType.DidFailToFinish>;
|
||||
|
||||
/** Action that indicates that flashing failed. */
|
||||
export function didFailToFinish(): FlashFirmwareDidFailToFinishAction {
|
||||
return { type: FlashFirmwareActionType.DidFailToFinish };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,4 +83,7 @@ export function progress(complete: number, total: number): FlashFirmwareProgress
|
||||
*/
|
||||
export type FlashFirmwareAction =
|
||||
| FlashFirmwareFlashAction
|
||||
| FlashFirmwareProgressAction;
|
||||
| FlashFirmwareDidStartAction
|
||||
| FlashFirmwareDidProgressAction
|
||||
| FlashFirmwareDidFinishAction
|
||||
| FlashFirmwareDidFailToFinishAction;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from '../actions';
|
||||
@@ -11,12 +11,18 @@ import OpenFileButton, { OpenFileButtonProps } from './OpenFileButton';
|
||||
import { TooltipId } from './button-i18n';
|
||||
import firmwareIcon from './images/firmware.svg';
|
||||
|
||||
type StateProps = Pick<OpenFileButtonProps, 'enabled'>;
|
||||
type StateProps = Pick<
|
||||
OpenFileButtonProps,
|
||||
'tooltip' | 'enabled' | 'showProgress' | 'progress'
|
||||
>;
|
||||
type DispatchProps = Pick<OpenFileButtonProps, 'onFile' | 'onReject' | 'onClick'>;
|
||||
type OwnProps = Pick<OpenFileButtonProps, 'id'>;
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
tooltip: state.firmware.flashing ? TooltipId.FlashProgress : TooltipId.Flash,
|
||||
enabled: state.bootloader.connection === BootloaderConnectionState.Disconnected,
|
||||
showProgress: state.firmware.flashing,
|
||||
progress: state.firmware.progress === null ? undefined : state.firmware.progress,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
@@ -39,7 +45,6 @@ const mergeProps = (
|
||||
ownProps: OwnProps,
|
||||
): OpenFileButtonProps => ({
|
||||
fileExtension: '.zip',
|
||||
tooltip: TooltipId.Flash,
|
||||
icon: firmwareIcon,
|
||||
...ownProps,
|
||||
...stateProps,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
||||
import { Button, Intent, Position, Spinner, Tooltip } from '@blueprintjs/core';
|
||||
import { WithI18nProps, withI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import Dropzone, { FileRejection } from 'react-dropzone';
|
||||
@@ -20,6 +20,10 @@ export interface OpenFileButtonProps {
|
||||
readonly icon: string;
|
||||
/** When true or undefined, the button is enabled. */
|
||||
readonly enabled?: boolean;
|
||||
/** Show progress spinner instead of icon. */
|
||||
readonly showProgress?: boolean;
|
||||
/** The progress value (0 to 1) for the progress spinner. */
|
||||
readonly progress?: number;
|
||||
/** Callback that is called when a file has been selected and opened for reading. */
|
||||
readonly onFile: (data: ArrayBuffer) => void;
|
||||
/** Callback that is called when a file has been rejected (e.g. bad file extension). */
|
||||
@@ -80,7 +84,19 @@ class OpenFileButton extends React.Component<Props> {
|
||||
>
|
||||
{({ getRootProps, getInputProps }): JSX.Element => (
|
||||
<Tooltip
|
||||
content={this.props.i18n.translate(this.props.tooltip)}
|
||||
content={this.props.i18n.translate(
|
||||
this.props.tooltip,
|
||||
this.props.tooltip === TooltipId.FlashProgress
|
||||
? {
|
||||
percent:
|
||||
this.props.progress === undefined
|
||||
? ''
|
||||
: this.props.i18n.formatPercentage(
|
||||
this.props.progress,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
)}
|
||||
position={Position.BOTTOM}
|
||||
hoverOpenDelay={tooltipDelay}
|
||||
>
|
||||
@@ -102,7 +118,14 @@ class OpenFileButton extends React.Component<Props> {
|
||||
: {})}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<img src={this.props.icon} alt={this.props.id} />
|
||||
{this.props.showProgress ? (
|
||||
<Spinner
|
||||
value={this.props.progress}
|
||||
intent={Intent.PRIMARY}
|
||||
/>
|
||||
) : (
|
||||
<img src={this.props.icon} alt={this.props.id} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -1,33 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { ProgressBar } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { RootState } from '../reducers';
|
||||
|
||||
import './status-bar.scss';
|
||||
|
||||
type StateProps = { progress: number };
|
||||
|
||||
type StatusProps = StateProps;
|
||||
|
||||
class StatusBar extends React.Component<StatusProps> {
|
||||
class StatusBar extends React.Component {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<div className="status-bar" onContextMenu={(e): void => e.preventDefault()}>
|
||||
<ProgressBar
|
||||
className="status-bar-item"
|
||||
value={this.props.progress}
|
||||
animate={false}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="status-bar"
|
||||
onContextMenu={(e): void => e.preventDefault()}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
progress: state.status.progress,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(StatusBar);
|
||||
export default connect()(StatusBar);
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"connect": { "tooltip": "Connect using Bluetooth" },
|
||||
"disconnect": { "tooltip": "Disconnect Bluetooth" }
|
||||
},
|
||||
"flash": { "tooltip": "Install Pybricks firmware" },
|
||||
"flash": {
|
||||
"action": { "tooltip": "Install Pybricks firmware" },
|
||||
"progress": { "tooltip": "Flashing… {percent}" }
|
||||
},
|
||||
"settings": { "tooltip": "Settings" }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
// File: components/button-i18n.ts
|
||||
// Button translation keys.
|
||||
|
||||
@@ -9,7 +9,8 @@ export enum TooltipId {
|
||||
Run = 'run.tooltip',
|
||||
Stop = 'stop.tooltip',
|
||||
Repl = 'repl.tooltip',
|
||||
Flash = 'flash.tooltip',
|
||||
Flash = 'flash.action.tooltip',
|
||||
FlashProgress = 'flash.progress.tooltip',
|
||||
BluetoothConnect = 'bluetooth.connect.tooltip',
|
||||
BluetoothDisconnect = 'bluetooth.disconnect.tooltip',
|
||||
Settings = 'settings.tooltip',
|
||||
|
||||
@@ -14,13 +14,3 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-bar-item {
|
||||
width: 25%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.#{$ns}-progress-bar.status-bar-item {
|
||||
// override progress bar default gray1 backgound
|
||||
background-color: $pt-app-background-color;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021 The Pybricks Authors
|
||||
|
||||
import { Reducer, combineReducers } from 'redux';
|
||||
import { Action } from '../actions';
|
||||
import { FlashFirmwareActionType } from '../actions/flash-firmware';
|
||||
|
||||
export interface FirmwareState {
|
||||
/** The firmware is being erased/flashed right now. */
|
||||
flashing: boolean;
|
||||
/** The current progress (0 to 1) or null for unknown (e.g erasing) */
|
||||
progress: number | null;
|
||||
}
|
||||
|
||||
const flashing: Reducer<boolean, Action> = (state = false, action) => {
|
||||
switch (action.type) {
|
||||
case FlashFirmwareActionType.DidStart:
|
||||
return true;
|
||||
case FlashFirmwareActionType.DidFinish:
|
||||
case FlashFirmwareActionType.DidFailToFinish:
|
||||
return false;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const progress: Reducer<number | null, Action> = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case FlashFirmwareActionType.DidStart:
|
||||
return null;
|
||||
case FlashFirmwareActionType.DidProgress:
|
||||
return action.value;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default combineReducers({ flashing, progress });
|
||||
@@ -1,15 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { combineReducers } from 'redux';
|
||||
import app, { AppState } from './app';
|
||||
import ble, { BleState } from './ble';
|
||||
import bootloader, { BootloaderState } from './bootloader';
|
||||
import editor, { EditorState } from './editor';
|
||||
import firmware, { FirmwareState } from './firmware';
|
||||
import hub, { HubState } from './hub';
|
||||
import license, { LicenseState } from './license';
|
||||
import settings, { SettingsState } from './settings';
|
||||
import status, { StatusState } from './status';
|
||||
import terminal, { TerminalState } from './terminal';
|
||||
|
||||
/**
|
||||
@@ -20,10 +20,10 @@ export interface RootState {
|
||||
readonly bootloader: BootloaderState;
|
||||
readonly ble: BleState;
|
||||
readonly editor: EditorState;
|
||||
readonly firmware: FirmwareState;
|
||||
readonly hub: HubState;
|
||||
readonly license: LicenseState;
|
||||
readonly settings: SettingsState;
|
||||
readonly status: StatusState;
|
||||
readonly terminal: TerminalState;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ export default combineReducers({
|
||||
bootloader,
|
||||
ble,
|
||||
editor,
|
||||
firmware,
|
||||
hub,
|
||||
license,
|
||||
settings,
|
||||
status,
|
||||
terminal,
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { Reducer } from 'react';
|
||||
import { combineReducers } from 'redux';
|
||||
import { Action } from '../actions';
|
||||
import { FlashFirmwareActionType } from '../actions/flash-firmware';
|
||||
|
||||
const progress: Reducer<number, Action> = (state = -1, action) => {
|
||||
switch (action.type) {
|
||||
case FlashFirmwareActionType.Progress:
|
||||
return action.complete / action.total;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export interface StatusState {
|
||||
readonly progress: number;
|
||||
}
|
||||
|
||||
export default combineReducers({ progress });
|
||||
@@ -21,7 +21,9 @@ import { Action } from '../actions';
|
||||
import {
|
||||
FlashFirmwareActionType,
|
||||
FlashFirmwareFlashAction,
|
||||
progress,
|
||||
didFinish,
|
||||
didProgress,
|
||||
didStart,
|
||||
} from '../actions/flash-firmware';
|
||||
import {
|
||||
BootloaderChecksumRequestAction,
|
||||
@@ -265,6 +267,8 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
}
|
||||
}
|
||||
|
||||
yield put(didStart());
|
||||
|
||||
const eraseAction = (yield put(eraseRequest())) as BootloaderEraseRequestAction;
|
||||
const [, erase] = (yield all([
|
||||
waitForDidSend(eraseAction.id),
|
||||
@@ -301,7 +305,7 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
)) as BootloaderProgramRequestAction;
|
||||
yield waitForDidSend(programAction.id);
|
||||
|
||||
yield put(progress(offset, firmware.length));
|
||||
yield put(didProgress(offset / firmware.length));
|
||||
|
||||
// we don't want to request checksum if this is the last packet since
|
||||
// the bootloader will send a response to the program request already.
|
||||
@@ -344,11 +348,13 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
|
||||
throw Error("Didn't flash all bytes");
|
||||
}
|
||||
|
||||
yield put(progress(firmware.length, firmware.length));
|
||||
yield put(didProgress(1));
|
||||
|
||||
// this will cause the remote device to disconnect and reboot
|
||||
const rebootAction = (yield put(rebootRequest())) as BootloaderRebootRequestAction;
|
||||
yield waitForDidSend(rebootAction.id);
|
||||
|
||||
yield put(didFinish());
|
||||
}
|
||||
|
||||
export default function* (): Generator {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ $pt-font-size-large: $pt-grid-size * 1.8;
|
||||
$pt-font-size-small: $pt-grid-size * 1.4;
|
||||
|
||||
$pt-navbar-height: 72px;
|
||||
$pb-status-bar-height: 3vh;
|
||||
$pb-status-bar-height: 24px;
|
||||
|
||||
$pb-pybricks-blue: #0088ce;
|
||||
$pt-app-background-color: #e8e8e8;
|
||||
|
||||
Reference in New Issue
Block a user