mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
wire up flash button
This commit is contained in:
@@ -4,6 +4,7 @@ import { AnyAction } from 'redux';
|
||||
import { connect as bleConnect, disconnect as bleDisconnect } from '../actions/ble';
|
||||
import { RootState } from '../reducers';
|
||||
import { BLEConnectionState } from '../reducers/ble';
|
||||
import { BootloaderConnectionState } from '../reducers/bootloader';
|
||||
import ActionButton, { ActionButtonProps } from './ActionButton';
|
||||
|
||||
type Dispatch = ThunkDispatch<{}, {}, AnyAction>;
|
||||
@@ -13,7 +14,10 @@ type StateProps = Pick<ButtonProps, 'tooltip' | 'icon' | 'context' | 'enabled'>;
|
||||
type DispatchProps = Pick<ButtonProps, 'onAction'>;
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => {
|
||||
if (state.ble.connection === BLEConnectionState.Disconnected) {
|
||||
if (
|
||||
state.ble.connection === BLEConnectionState.Disconnected &&
|
||||
state.bootloader.connection === BootloaderConnectionState.Disconnected
|
||||
) {
|
||||
return {
|
||||
tooltip: 'Connect using Bluetooth',
|
||||
icon: 'btdisconnected.svg',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { AnyAction } from 'redux';
|
||||
import { RootState } from '../reducers';
|
||||
import { flashFirmware } from '../actions/bootloader';
|
||||
import { BootloaderConnectionState } from '../reducers/bootloader';
|
||||
import OpenFileButton, { OpenFileButtonProps } from './OpenFileButton';
|
||||
|
||||
type Dispatch = ThunkDispatch<{}, {}, AnyAction>;
|
||||
|
||||
type StateProps = Pick<OpenFileButtonProps, 'enabled'>;
|
||||
type DispatchProps = Pick<OpenFileButtonProps, 'onFile'>;
|
||||
type OwnProps = Pick<OpenFileButtonProps, 'id'>;
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
enabled: state.bootloader.connection === BootloaderConnectionState.Disconnected,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
onFile: (data): void => {
|
||||
dispatch(flashFirmware(data));
|
||||
},
|
||||
});
|
||||
|
||||
const mergeProps = (
|
||||
stateProps: StateProps,
|
||||
dispatchProps: DispatchProps,
|
||||
ownProps: OwnProps,
|
||||
): OpenFileButtonProps => ({
|
||||
fileExtension: '.bin',
|
||||
tooltip: 'Flash hub firmware',
|
||||
icon: 'firmware.svg',
|
||||
...ownProps,
|
||||
...stateProps,
|
||||
...dispatchProps,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(OpenFileButton);
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import Tooltip from 'react-bootstrap/Tooltip';
|
||||
import Image from 'react-bootstrap/Image';
|
||||
|
||||
export interface OpenFileButtonProps {
|
||||
/** A unique id for each instance. */
|
||||
readonly id: string;
|
||||
/** The accepted file extension */
|
||||
readonly fileExtension: string;
|
||||
/** Tooltip text that appears when hovering over the button. */
|
||||
readonly tooltip: string;
|
||||
/** Icon shown on the button. */
|
||||
readonly icon: string;
|
||||
/** When true or undefined, the button is enabled. */
|
||||
readonly enabled?: boolean;
|
||||
/** Callback that is called when the button is activated (clicked). */
|
||||
readonly onFile: (data: ArrayBuffer) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Button that opens a file chooser dialog or accepts files dropped on it.
|
||||
*/
|
||||
class OpenFileButton extends React.Component<OpenFileButtonProps> {
|
||||
constructor(props: OpenFileButtonProps) {
|
||||
super(props);
|
||||
this.onDropAccepted = this.onDropAccepted.bind(this);
|
||||
this.onDropRejected = this.onDropRejected.bind(this);
|
||||
}
|
||||
|
||||
private onDropAccepted(acceptedFiles: File[]): void {
|
||||
// should only be one file since multiple={false}
|
||||
acceptedFiles.forEach((f) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onabort = (): void => console.error('file reading was aborted');
|
||||
reader.onerror = (): void => console.error('file reading has failed');
|
||||
reader.onload = (): void => {
|
||||
const binaryStr = reader.result;
|
||||
if (binaryStr === null) {
|
||||
throw Error('Unexpected null binaryStr');
|
||||
}
|
||||
if (typeof binaryStr === 'string') {
|
||||
throw Error('Unexpected string binaryStr');
|
||||
}
|
||||
this.props.onFile(binaryStr);
|
||||
};
|
||||
reader.readAsArrayBuffer(f);
|
||||
});
|
||||
}
|
||||
|
||||
private onDropRejected(rejectedFiles: File[]): void {
|
||||
// should only be one file since multiple={false}
|
||||
rejectedFiles.forEach((f) => {
|
||||
// TODO: proper bootstrap toast
|
||||
alert(`bad file ${f.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Dropzone
|
||||
onDropAccepted={this.onDropAccepted}
|
||||
onDropRejected={this.onDropRejected}
|
||||
accept={this.props.fileExtension}
|
||||
multiple={false}
|
||||
>
|
||||
{({ getRootProps, getInputProps }): JSX.Element => (
|
||||
<OverlayTrigger
|
||||
placement="bottom"
|
||||
overlay={
|
||||
<Tooltip id={`${this.props.id}-tooltip`}>
|
||||
{this.props.tooltip}.
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
{...getRootProps()}
|
||||
variant="primary"
|
||||
disabled={this.props.enabled === false}
|
||||
style={
|
||||
this.props.enabled === false
|
||||
? { pointerEvents: 'none' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Image
|
||||
src={`/static/images/${this.props.icon}`}
|
||||
alt={this.props.id}
|
||||
/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OpenFileButton;
|
||||
@@ -3,32 +3,13 @@ import Col from 'react-bootstrap/Col';
|
||||
import ButtonGroup from 'react-bootstrap/ButtonGroup';
|
||||
import ButtonToolbar from 'react-bootstrap/ButtonToolbar';
|
||||
import React from 'react';
|
||||
import { BLEConnectionState } from '../reducers/ble';
|
||||
import ActionButton from './ActionButton';
|
||||
import BluetoothButton from './BluetoothButton';
|
||||
import RunButton from './RunButton';
|
||||
import StopButton from './StopButton';
|
||||
import ReplButton from './ReplButton';
|
||||
import FlashButton from './FlashButton';
|
||||
|
||||
interface ToolbarState {
|
||||
bleState: BLEConnectionState;
|
||||
}
|
||||
|
||||
class Toolbar extends React.Component<{}, ToolbarState> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = { bleState: BLEConnectionState.Disconnected };
|
||||
this.onAction = this.onAction.bind(this);
|
||||
}
|
||||
|
||||
private onAction(action?: string): void {
|
||||
console.log(action);
|
||||
}
|
||||
|
||||
setBLEState(state: BLEConnectionState): void {
|
||||
this.setState({ bleState: state });
|
||||
}
|
||||
|
||||
class Toolbar extends React.Component {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Row>
|
||||
@@ -41,12 +22,7 @@ class Toolbar extends React.Component<{}, ToolbarState> {
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className="mr-2" size="lg">
|
||||
<ReplButton id="repl" />
|
||||
<ActionButton
|
||||
id="flash"
|
||||
tooltip="Flash hub firmware"
|
||||
icon="firmware.svg"
|
||||
onAction={this.onAction}
|
||||
/>
|
||||
<FlashButton id="flash" />
|
||||
</ButtonGroup>
|
||||
</ButtonToolbar>
|
||||
</Col>
|
||||
|
||||
Reference in New Issue
Block a user