mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
+5
-2
@@ -2,10 +2,11 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
## [1.2.0-beta.1] - 2021-12-27
|
||||
|
||||
### Added
|
||||
- Status bar indicator for connected hub.
|
||||
- Basic battery OK/low indicator ([support#559]).
|
||||
|
||||
### Changed
|
||||
- Saving file now uses proper save dialog in Chromium browser ([support#84]).
|
||||
@@ -17,6 +18,7 @@
|
||||
[support#84]: https://github.com/pybricks/support/issues/84
|
||||
[support#300]: https://github.com/pybricks/support/issues/300
|
||||
[support#369]: https://github.com/pybricks/support/issues/369
|
||||
[support#559]: https://github.com/pybricks/support/issues/559
|
||||
|
||||
## [1.1.0] - 2021-12-16
|
||||
|
||||
@@ -235,7 +237,8 @@ Prerelease changes are documented at [support#48].
|
||||
|
||||
<!-- links for version headings -->
|
||||
|
||||
[Unreleased]: https://github.com/pybricks/pybricks-code/compare/v1.1.0...HEAD
|
||||
[Unreleased]: https://github.com/pybricks/pybricks-code/compare/v1.2.0-beta.1...HEAD
|
||||
[1.2.0-beta.1]: https://github.com/pybricks/pybricks-code/compare/v1.1.0...v1.2.0-beta.1
|
||||
[1.1.0]: https://github.com/pybricks/pybricks-code/compare/v1.1.0-rc.1...v1.1.0
|
||||
[1.1.0-rc.1]: https://github.com/pybricks/pybricks-code/compare/v1.1.0-beta.6...v1.1.0-rc.1
|
||||
[1.1.0-beta.6]: https://github.com/pybricks/pybricks-code/compare/v1.1.0-beta.5...v1.1.0-beta.6
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pybricks/pybricks-code",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0-beta.1",
|
||||
"license": "MIT",
|
||||
"author": "The Pybricks Authors",
|
||||
"repository": {
|
||||
|
||||
+8
-2
@@ -1,15 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { useDispatch as useReduxDispatch } from 'react-redux';
|
||||
import { Dispatch as ReduxDispatch } from 'redux';
|
||||
import { AppAction } from './app/actions';
|
||||
import { BleDIServiceAction } from './ble-device-info-service/actions';
|
||||
import { BleUartAction } from './ble-nordic-uart-service/actions';
|
||||
import {
|
||||
BlePybricksServiceAction,
|
||||
BlePybricksServiceCommandAction,
|
||||
BlePybricksServiceEventAction,
|
||||
} from './ble-pybricks-service/actions';
|
||||
import { BleUartAction } from './ble-uart/actions';
|
||||
import { BLEAction, BLEConnectAction } from './ble/actions';
|
||||
import { EditorAction } from './editor/actions';
|
||||
import { FlashFirmwareAction } from './firmware/actions';
|
||||
@@ -60,3 +61,8 @@ export type Action =
|
||||
* Dispatch function.
|
||||
*/
|
||||
export type Dispatch = ReduxDispatch<Action>;
|
||||
|
||||
/**
|
||||
* Typed version of Redux useDispatch() hook.
|
||||
*/
|
||||
export const useDispatch = (): Dispatch => useReduxDispatch<Dispatch>();
|
||||
|
||||
+17
-4
@@ -2,11 +2,11 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import SplitterLayout from 'react-splitter-layout';
|
||||
import { useDispatch } from '../actions';
|
||||
import Editor from '../editor/Editor';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import { toggleBoolean } from '../settings/actions';
|
||||
import { BooleanSettingId } from '../settings/defaults';
|
||||
import StatusBar from '../status-bar/StatusBar';
|
||||
@@ -122,9 +122,22 @@ const Docs: React.FunctionComponent = (_props) => {
|
||||
};
|
||||
|
||||
const App: React.FunctionComponent = (_props) => {
|
||||
const showDocs = useSelector((s: RootState): boolean => s.settings.showDocs);
|
||||
const darkMode = useSelector((s): boolean => s.settings.darkMode);
|
||||
const showDocs = useSelector((s): boolean => s.settings.showDocs);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
// darkMode class has to be applied to body element, otherwise it won't
|
||||
// affect portals
|
||||
useEffect(() => {
|
||||
if (!darkMode) {
|
||||
// no class for light mode, so nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.classList.add(Classes.DARK);
|
||||
return () => document.body.classList.remove(Classes.DARK);
|
||||
}, [darkMode]);
|
||||
|
||||
return (
|
||||
<div className="pb-app h-100 w-100 p-absolute">
|
||||
<Toolbar />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
// actions/ble-uart.ts: Actions for Bluetooth Low Energy nRF UART service
|
||||
//
|
||||
// Actions for Bluetooth Low Energy Nordic UART service
|
||||
|
||||
import { Action } from 'redux';
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from '../ble-device-info-service/actions';
|
||||
import { PnpIdVendorIdSource } from '../ble-device-info-service/protocol';
|
||||
import { HubType, LegoCompanyId } from '../ble-lwp3-service/protocol';
|
||||
import { didReceiveStatusReport } from '../ble-pybricks-service/actions';
|
||||
import { Status, statusToFlag } from '../ble-pybricks-service/protocol';
|
||||
import {
|
||||
BleDeviceDidFailToConnectReason,
|
||||
connect,
|
||||
@@ -25,7 +27,9 @@ test('initial state', () => {
|
||||
expect(reducers(undefined, {} as Action)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"connection": "ble.connection.state.disconnected",
|
||||
"deviceBatteryCharging": false,
|
||||
"deviceFirmwareVersion": "",
|
||||
"deviceLowBatteryWarning": false,
|
||||
"deviceName": "",
|
||||
"deviceType": "",
|
||||
}
|
||||
@@ -113,3 +117,31 @@ test('deviceFirmwareVersion', () => {
|
||||
.deviceFirmwareVersion,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceLowBatteryWarning', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceLowBatteryWarning: false } as State,
|
||||
didReceiveStatusReport(statusToFlag(Status.BatteryLowVoltageWarning)),
|
||||
).deviceLowBatteryWarning,
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceLowBatteryWarning: true } as State,
|
||||
didReceiveStatusReport(~statusToFlag(Status.BatteryLowVoltageWarning)),
|
||||
).deviceLowBatteryWarning,
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
reducers({ deviceLowBatteryWarning: true } as State, didDisconnect())
|
||||
.deviceLowBatteryWarning,
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('deviceBatteryCharging', () => {
|
||||
expect(
|
||||
reducers({ deviceBatteryCharging: true } as State, didDisconnect())
|
||||
.deviceBatteryCharging,
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -8,6 +8,8 @@ import { Reducer, combineReducers } from 'redux';
|
||||
import { Action } from '../actions';
|
||||
import { BleDIServiceActionType } from '../ble-device-info-service/actions';
|
||||
import { getHubTypeName } from '../ble-device-info-service/protocol';
|
||||
import { BlePybricksServiceEventActionType } from '../ble-pybricks-service/actions';
|
||||
import { Status, statusToFlag } from '../ble-pybricks-service/protocol';
|
||||
import { BleDeviceActionType } from './actions';
|
||||
|
||||
/**
|
||||
@@ -85,9 +87,34 @@ const deviceFirmwareVersion: Reducer<string, Action> = (state = '', action) => {
|
||||
}
|
||||
};
|
||||
|
||||
const deviceLowBatteryWarning: Reducer<boolean, Action> = (state = false, action) => {
|
||||
switch (action.type) {
|
||||
case BleDeviceActionType.DidDisconnect:
|
||||
return false;
|
||||
case BlePybricksServiceEventActionType.DidReceiveStatusReport:
|
||||
return Boolean(
|
||||
action.statusFlags & statusToFlag(Status.BatteryLowVoltageWarning),
|
||||
);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const deviceBatteryCharging: Reducer<boolean, Action> = (state = false, action) => {
|
||||
switch (action.type) {
|
||||
case BleDeviceActionType.DidDisconnect:
|
||||
return false;
|
||||
// TODO: hub does not currently have a status flag for this
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
connection,
|
||||
deviceName,
|
||||
deviceType,
|
||||
deviceFirmwareVersion,
|
||||
deviceLowBatteryWarning,
|
||||
deviceBatteryCharging,
|
||||
});
|
||||
|
||||
@@ -27,6 +27,18 @@ import {
|
||||
pnpIdUUID,
|
||||
softwareRevisionStringUUID,
|
||||
} from '../ble-device-info-service/protocol';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartWriteAction,
|
||||
didFailToWrite as didFailToWriteUart,
|
||||
didNotify as didNotifyUart,
|
||||
didWrite as didWriteUart,
|
||||
} from '../ble-nordic-uart-service/actions';
|
||||
import {
|
||||
RxCharUUID as uartRxCharUUID,
|
||||
ServiceUUID as uartServiceUUID,
|
||||
TxCharUUID as uartTxCharUUID,
|
||||
} from '../ble-nordic-uart-service/protocol';
|
||||
import {
|
||||
BlePybricksServiceActionType,
|
||||
didFailToWriteCommand,
|
||||
@@ -37,6 +49,8 @@ import {
|
||||
ControlCharacteristicUUID as pybricksCommandCharacteristicUUID,
|
||||
ServiceUUID as pybricksServiceUUID,
|
||||
} from '../ble-pybricks-service/protocol';
|
||||
import { RootState } from '../reducers';
|
||||
import { ensureError } from '../utils';
|
||||
import {
|
||||
BLEActionType,
|
||||
BleDeviceActionType as BLEDeviceActionType,
|
||||
@@ -49,22 +63,8 @@ import {
|
||||
didDisconnect,
|
||||
didFailToConnect,
|
||||
disconnect as disconnectAction,
|
||||
} from '../ble/actions';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { RootState } from '../reducers';
|
||||
import { ensureError } from '../utils';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartWriteAction,
|
||||
didFailToWrite as didFailToWriteUart,
|
||||
didNotify as didNotifyUart,
|
||||
didWrite as didWriteUart,
|
||||
} from './actions';
|
||||
import {
|
||||
RxCharUUID as uartRxCharUUID,
|
||||
ServiceUUID as uartServiceUUID,
|
||||
TxCharUUID as uartTxCharUUID,
|
||||
} from './protocol';
|
||||
import { BleConnectionState } from './reducers';
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
@@ -12,10 +12,10 @@ import tomorrowNightEightiesTheme from 'monaco-themes/themes/Tomorrow-Night-Eigh
|
||||
import xcodeTheme from 'monaco-themes/themes/Xcode_default.json';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import MonacoEditor, { monaco } from 'react-monaco-editor';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { useDispatch } from '../actions';
|
||||
import { compile } from '../mpy/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import { toggleBoolean } from '../settings/actions';
|
||||
import { BooleanSettingId } from '../settings/defaults';
|
||||
import { isMacOS } from '../utils/os';
|
||||
@@ -63,7 +63,7 @@ const xcodeId = 'xcode';
|
||||
monaco.editor.defineTheme(xcodeId, xcodeTheme as monaco.editor.IStandaloneThemeData);
|
||||
|
||||
const contextMenu = (_props: ContextMenu2ContentProps): JSX.Element => {
|
||||
const editor = useSelector((state: RootState) => state.editor.current);
|
||||
const editor = useSelector((s) => s.editor.current);
|
||||
|
||||
const [i18n] = useI18n({ id: 'editor', translations: { en }, fallback: en });
|
||||
|
||||
@@ -143,7 +143,7 @@ const Editor: React.FunctionComponent = (_props) => {
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
});
|
||||
|
||||
const darkMode = useSelector((state: RootState) => state.settings.darkMode);
|
||||
const darkMode = useSelector((s) => s.settings.darkMode);
|
||||
|
||||
const [i18n] = useI18n({ id: 'editor', translations: { en }, fallback: en });
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from '../actions';
|
||||
import * as notificationActions from '../notifications/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import OpenFileButton, { OpenFileButtonProps } from '../toolbar/OpenFileButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import * as editorActions from './actions';
|
||||
@@ -13,7 +13,7 @@ import openIcon from './open.svg';
|
||||
type OpenButtonProps = Pick<OpenFileButtonProps, 'id'>;
|
||||
|
||||
const OpenButton: React.FunctionComponent<OpenButtonProps> = (props) => {
|
||||
const editor = useSelector((state: RootState) => state.editor.current);
|
||||
const editor = useSelector((s) => s.editor.current);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from '../actions';
|
||||
import * as editorActions from '../editor/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import downloadIcon from './save.svg';
|
||||
@@ -13,7 +13,7 @@ type SaveAsButtonProps = Pick<ActionButtonProps, 'id'> &
|
||||
Pick<ActionButtonProps, 'keyboardShortcut'>;
|
||||
|
||||
const SaveAsButton: React.FunctionComponent<SaveAsButtonProps> = (props) => {
|
||||
const editor = useSelector((state: RootState) => state.editor.current);
|
||||
const editor = useSelector((s) => s.editor.current);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { AsyncSaga } from '../../test';
|
||||
import { didFailToWrite } from '../ble-nordic-uart-service/actions';
|
||||
import { eventProtocolError } from '../ble-pybricks-service/actions';
|
||||
import { didFailToWrite } from '../ble-uart/actions';
|
||||
import {
|
||||
BleDeviceFailToConnectReasonType,
|
||||
didFailToConnect as bleDidFailToConnect,
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { takeEvery } from 'typed-redux-saga/macro';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartDidFailToWriteAction,
|
||||
} from '../ble-nordic-uart-service/actions';
|
||||
import {
|
||||
BlePybricksServiceEventActionType,
|
||||
BlePybricksServiceEventProtocolErrorAction,
|
||||
} from '../ble-pybricks-service/actions';
|
||||
import { BleUartActionType, BleUartDidFailToWriteAction } from '../ble-uart/actions';
|
||||
import {
|
||||
BleDeviceActionType,
|
||||
BleDeviceDidFailToConnectAction,
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from '../actions';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { BootloaderConnectionState } from '../lwp3-bootloader/reducers';
|
||||
import * as notificationActions from '../notifications/actions';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import OpenFileButton, { OpenFileButtonProps } from '../toolbar/OpenFileButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import { flashFirmware } from './actions';
|
||||
@@ -15,12 +15,10 @@ import firmwareIcon from './firmware.svg';
|
||||
type FlashButtonProps = Pick<OpenFileButtonProps, 'id'>;
|
||||
|
||||
const FlashButton: React.FunctionComponent<FlashButtonProps> = (props) => {
|
||||
const bootloaderConnection = useSelector(
|
||||
(state: RootState) => state.bootloader.connection,
|
||||
);
|
||||
const bleConnection = useSelector((state: RootState) => state.ble.connection);
|
||||
const flashing = useSelector((state: RootState) => state.firmware.flashing);
|
||||
const progress = useSelector((state: RootState) => state.firmware.progress);
|
||||
const bootloaderConnection = useSelector((s) => s.bootloader.connection);
|
||||
const bleConnection = useSelector((s) => s.ble.connection);
|
||||
const flashing = useSelector((s) => s.firmware.flashing);
|
||||
const progress = useSelector((s) => s.firmware.progress);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from '../actions';
|
||||
import { toggleBluetooth } from '../ble/actions';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { BootloaderConnectionState } from '../lwp3-bootloader/reducers';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import btConnectedIcon from './bt-connected.svg';
|
||||
@@ -15,10 +15,8 @@ import btDisconnectedIcon from './bt-disconnected.svg';
|
||||
type BluetoothButtonProps = Pick<ActionButtonProps, 'id'>;
|
||||
|
||||
const BluetoothButton: React.FunctionComponent<BluetoothButtonProps> = (props) => {
|
||||
const bootloaderConnection = useSelector(
|
||||
(state: RootState) => state.bootloader.connection,
|
||||
);
|
||||
const bleConnection = useSelector((state: RootState) => state.ble.connection);
|
||||
const bootloaderConnection = useSelector((s) => s.bootloader.connection);
|
||||
const bleConnection = useSelector((s) => s.ble.connection);
|
||||
|
||||
const isDisconnected =
|
||||
bootloaderConnection === BootloaderConnectionState.Disconnected &&
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from '../reducers';
|
||||
import { useDispatch } from '../actions';
|
||||
import { useSelector } from '../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import { downloadAndRun } from './actions';
|
||||
@@ -14,11 +14,9 @@ type RunButtonProps = Pick<ActionButtonProps, 'id'> &
|
||||
Pick<ActionButtonProps, 'keyboardShortcut'>;
|
||||
|
||||
const RunButton: React.FunctionComponent<RunButtonProps> = (props) => {
|
||||
const editor = useSelector((state: RootState) => state.editor.current);
|
||||
const downloadProgress = useSelector(
|
||||
(state: RootState) => state.hub.downloadProgress,
|
||||
);
|
||||
const runtime = useSelector((state: RootState) => state.hub.runtime);
|
||||
const editor = useSelector((s) => s.editor.current);
|
||||
const downloadProgress = useSelector((s) => s.hub.downloadProgress);
|
||||
const runtime = useSelector((s) => s.hub.runtime);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from '../reducers';
|
||||
import { useDispatch } from '../actions';
|
||||
import { useSelector } from '../reducers';
|
||||
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
|
||||
import { TooltipId } from '../toolbar/i18n';
|
||||
import { stop } from './actions';
|
||||
@@ -14,7 +14,7 @@ type StopButtonProps = Pick<ActionButtonProps, 'id'> &
|
||||
Pick<ActionButtonProps, 'keyboardShortcut'>;
|
||||
|
||||
const StopButton: React.FunctionComponent<StopButtonProps> = (props) => {
|
||||
const runtime = useSelector((state: RootState) => state.hub.runtime);
|
||||
const runtime = useSelector((s) => s.hub.runtime);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { monaco } from 'react-monaco-editor';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartWriteAction,
|
||||
didWrite,
|
||||
} from '../ble-nordic-uart-service/actions';
|
||||
import {
|
||||
BlePybricksServiceCommandActionType,
|
||||
BlePybricksServiceCommandSendStopUserProgram,
|
||||
didSendCommand,
|
||||
} from '../ble-pybricks-service/actions';
|
||||
import { BleUartActionType, BleUartWriteAction, didWrite } from '../ble-uart/actions';
|
||||
import { MpyActionType, didCompile } from '../mpy/actions';
|
||||
import { createCountFunc } from '../utils/iter';
|
||||
import {
|
||||
|
||||
+7
-7
@@ -13,19 +13,19 @@ import {
|
||||
takeEvery,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import { Action } from '../actions';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartDidFailToWriteAction,
|
||||
BleUartDidWriteAction,
|
||||
write,
|
||||
} from '../ble-nordic-uart-service/actions';
|
||||
import { SafeTxCharLength } from '../ble-nordic-uart-service/protocol';
|
||||
import {
|
||||
BlePybricksServiceCommandActionType,
|
||||
BlePybricksServiceCommandDidFailToSendAction,
|
||||
BlePybricksServiceCommandDidSendAction,
|
||||
sendStopUserProgramCommand,
|
||||
} from '../ble-pybricks-service/actions';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartDidFailToWriteAction,
|
||||
BleUartDidWriteAction,
|
||||
write,
|
||||
} from '../ble-uart/actions';
|
||||
import { SafeTxCharLength } from '../ble-uart/protocol';
|
||||
import { BleDeviceActionType } from '../ble/actions';
|
||||
import {
|
||||
MpyActionType,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import { I18nContext } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
@@ -41,30 +40,6 @@ const store = createStore(
|
||||
applyMiddleware(sagaMiddleware, loggerMiddleware),
|
||||
);
|
||||
|
||||
// Hook in blueprints dark mode class to setting
|
||||
let oldDarkMode = false;
|
||||
store.subscribe(() => {
|
||||
const newDarkMode = store.getState().settings.darkMode;
|
||||
if (newDarkMode !== oldDarkMode) {
|
||||
if (newDarkMode) {
|
||||
document.body.classList.add(Classes.DARK);
|
||||
for (const frame of document.getElementsByTagName('iframe')) {
|
||||
frame.contentWindow?.document.documentElement.classList.add(
|
||||
Classes.DARK,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
document.body.classList.remove(Classes.DARK);
|
||||
for (const frame of document.getElementsByTagName('iframe')) {
|
||||
frame.contentWindow?.document.documentElement.classList.remove(
|
||||
Classes.DARK,
|
||||
);
|
||||
}
|
||||
}
|
||||
oldDarkMode = newDarkMode;
|
||||
}
|
||||
});
|
||||
|
||||
// special styling for beta versions
|
||||
if (appVersion.match(/beta/)) {
|
||||
document.body.classList.add('pb-beta');
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
} from '@blueprintjs/core';
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from '../actions';
|
||||
import { appName } from '../app/constants';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import { fetchList, select } from './actions';
|
||||
import { LicenseStringId } from './i18n';
|
||||
import en from './i18n.en.json';
|
||||
@@ -31,7 +31,7 @@ type LicenseListPanelProps = {
|
||||
const LicenseListPanel: React.VoidFunctionComponent<LicenseListPanelProps> = (
|
||||
props,
|
||||
) => {
|
||||
const licenseList = useSelector((state: RootState) => state.licenses.list);
|
||||
const licenseList = useSelector((s) => s.licenses.list);
|
||||
|
||||
return (
|
||||
<div className="pb-license-list">
|
||||
@@ -54,7 +54,7 @@ const LicenseListPanel: React.VoidFunctionComponent<LicenseListPanelProps> = (
|
||||
};
|
||||
|
||||
const LicenseInfoPanel = React.forwardRef<HTMLDivElement>((_props, ref) => {
|
||||
const licenseInfo = useSelector((state: RootState) => state.licenses.selected);
|
||||
const licenseInfo = useSelector((s) => s.licenses.selected);
|
||||
|
||||
const [i18n] = useI18n({ id: 'license', translations: { en }, fallback: en });
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { TypedUseSelectorHook, useSelector as useReduxSelector } from 'react-redux';
|
||||
import { Reducer, combineReducers } from 'redux';
|
||||
import app from './app/reducers';
|
||||
import ble from './ble/reducers';
|
||||
@@ -31,3 +32,8 @@ export const rootReducer = combineReducers({
|
||||
type StateFromReducer<R> = R extends Reducer<infer S> ? S : never;
|
||||
|
||||
export type RootState = StateFromReducer<typeof rootReducer>;
|
||||
|
||||
/**
|
||||
* Typed version of react-redux useSelector() hook.
|
||||
*/
|
||||
export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector;
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import { all, put } from 'typed-redux-saga/macro';
|
||||
import { didStart } from './app/actions';
|
||||
import app from './app/sagas';
|
||||
import blePybricksService from './ble-pybricks-service/sagas';
|
||||
import bleUart from './ble-uart/sagas';
|
||||
import ble from './ble/sagas';
|
||||
import editor from './editor/sagas';
|
||||
import errorLog from './error-log/sagas';
|
||||
import flashFirmware from './firmware/sagas';
|
||||
@@ -23,7 +23,7 @@ export default function* (): Generator {
|
||||
yield* all([
|
||||
app(),
|
||||
blePybricksService(),
|
||||
bleUart(),
|
||||
ble(),
|
||||
lwp3BootloaderBle(),
|
||||
lwp3BootloaderProtocol(),
|
||||
editor(),
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
import { Tooltip2 } from '@blueprintjs/popover2';
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AboutDialog from '../about/AboutDialog';
|
||||
import { useDispatch } from '../actions';
|
||||
import { checkForUpdate, installPrompt, reload } from '../app/actions';
|
||||
import {
|
||||
pybricksBugReportsUrl,
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
tooltipDelay,
|
||||
} from '../app/constants';
|
||||
import { pseudolocalize } from '../i18n';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import ExternalLinkIcon from '../utils/ExternalLinkIcon';
|
||||
import { isMacOS } from '../utils/os';
|
||||
import { setBoolean, setString, toggleBoolean } from './actions';
|
||||
@@ -47,31 +47,17 @@ type SettingsProps = {
|
||||
const SettingsDrawer: React.FunctionComponent<SettingsProps> = (props) => {
|
||||
const [isAboutDialogOpen, setIsAboutDialogOpen] = useState(false);
|
||||
|
||||
const showDocs = useSelector((state: RootState) => state.settings.showDocs);
|
||||
const darkMode = useSelector((state: RootState) => state.settings.darkMode);
|
||||
const flashCurrentProgram = useSelector(
|
||||
(state: RootState) => state.settings.flashCurrentProgram,
|
||||
);
|
||||
const serviceWorker = useSelector((state: RootState) => state.app.serviceWorker);
|
||||
const checkingForUpdate = useSelector(
|
||||
(state: RootState) => state.app.checkingForUpdate,
|
||||
);
|
||||
const updateAvailable = useSelector(
|
||||
(state: RootState) => state.app.updateAvailable,
|
||||
);
|
||||
const beforeInstallPrompt = useSelector(
|
||||
(state: RootState) => state.app.beforeInstallPrompt,
|
||||
);
|
||||
const promptingInstall = useSelector(
|
||||
(state: RootState) => state.app.promptingInstall,
|
||||
);
|
||||
const readyForOfflineUse = useSelector(
|
||||
(state: RootState) => state.app.readyForOfflineUse,
|
||||
);
|
||||
const hubName = useSelector((state: RootState) => state.settings.hubName);
|
||||
const isHubNameValid = useSelector(
|
||||
(state: RootState) => state.settings.isHubNameValid,
|
||||
);
|
||||
const showDocs = useSelector((s) => s.settings.showDocs);
|
||||
const darkMode = useSelector((s) => s.settings.darkMode);
|
||||
const flashCurrentProgram = useSelector((s) => s.settings.flashCurrentProgram);
|
||||
const serviceWorker = useSelector((s) => s.app.serviceWorker);
|
||||
const checkingForUpdate = useSelector((s) => s.app.checkingForUpdate);
|
||||
const updateAvailable = useSelector((s) => s.app.updateAvailable);
|
||||
const beforeInstallPrompt = useSelector((s) => s.app.beforeInstallPrompt);
|
||||
const promptingInstall = useSelector((s) => s.app.promptingInstall);
|
||||
const readyForOfflineUse = useSelector((s) => s.app.readyForOfflineUse);
|
||||
const hubName = useSelector((s) => s.settings.hubName);
|
||||
const isHubNameValid = useSelector((s) => s.settings.isHubNameValid);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2021 The Pybricks Authors
|
||||
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { Classes as Classes2, Popover2 } from '@blueprintjs/popover2';
|
||||
import { Button, Intent, ProgressBar } from '@blueprintjs/core';
|
||||
import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2';
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { RootState } from '../reducers';
|
||||
import { useSelector } from '../reducers';
|
||||
import { MessageId } from './i18n';
|
||||
import en from './i18n.en.json';
|
||||
|
||||
import './status-bar.scss';
|
||||
|
||||
const commonPopoverProps: Partial<Popover2Props> = {
|
||||
popoverClassName: Classes2.POPOVER2_CONTENT_SIZING,
|
||||
placement: 'top',
|
||||
};
|
||||
|
||||
const HubInfoButton: React.VFC = (_props) => {
|
||||
const deviceName = useSelector((state: RootState) => state.ble.deviceName);
|
||||
const deviceType = useSelector((state: RootState) => state.ble.deviceType);
|
||||
const deviceFirmwareVersion = useSelector(
|
||||
(state: RootState) => state.ble.deviceFirmwareVersion,
|
||||
);
|
||||
const deviceName = useSelector((s) => s.ble.deviceName);
|
||||
const deviceType = useSelector((s) => s.ble.deviceType);
|
||||
const deviceFirmwareVersion = useSelector((s) => s.ble.deviceFirmwareVersion);
|
||||
|
||||
const [i18n] = useI18n({ id: 'statusBar', translations: { en }, fallback: en });
|
||||
|
||||
return (
|
||||
<Popover2
|
||||
popoverClassName={Classes2.POPOVER2_CONTENT_SIZING}
|
||||
rootBoundary="document"
|
||||
placement="top"
|
||||
{...commonPopoverProps}
|
||||
content={
|
||||
<table className="no-wrap">
|
||||
<tbody>
|
||||
@@ -65,8 +65,39 @@ const HubInfoButton: React.VFC = (_props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const BatteryIndicator: React.VFC = (_props) => {
|
||||
const charging = useSelector((s) => s.ble.deviceBatteryCharging);
|
||||
const lowBatteryWarning = useSelector((s) => s.ble.deviceLowBatteryWarning);
|
||||
|
||||
const [i18n] = useI18n({ id: 'statusBar', translations: { en }, fallback: en });
|
||||
|
||||
return (
|
||||
<Popover2
|
||||
{...commonPopoverProps}
|
||||
content={
|
||||
<span className="no-wrap">
|
||||
{i18n.translate(
|
||||
lowBatteryWarning ? MessageId.BatteryLow : MessageId.BatteryOk,
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className="pb-battery-indicator" style={{ cursor: 'pointer' }}>
|
||||
<div className="pb-battery-indicator-body">
|
||||
<ProgressBar
|
||||
animate={charging}
|
||||
stripes={charging}
|
||||
intent={lowBatteryWarning ? Intent.DANGER : Intent.SUCCESS}
|
||||
/>
|
||||
</div>
|
||||
<div className="pb-battery-indicator-tip" />
|
||||
</div>
|
||||
</Popover2>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusBar: React.VFC = (_props) => {
|
||||
const connection = useSelector((state: RootState) => state.ble.connection);
|
||||
const connection = useSelector((s) => s.ble.connection);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -75,7 +106,12 @@ const StatusBar: React.VFC = (_props) => {
|
||||
aria-live="off"
|
||||
onContextMenu={(e): void => e.preventDefault()}
|
||||
>
|
||||
{connection === BleConnectionState.Connected && <HubInfoButton />}
|
||||
{connection === BleConnectionState.Connected && (
|
||||
<>
|
||||
<HubInfoButton />
|
||||
<BatteryIndicator />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,5 +3,9 @@
|
||||
"connectedTo": "Connected to:",
|
||||
"hubType": "Hub type:",
|
||||
"firmware": "Firmware:"
|
||||
},
|
||||
"battery": {
|
||||
"low": "Battery is low. Hub will turn off soon.",
|
||||
"ok": "Battery level is OK."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// Status bar translation keys.
|
||||
|
||||
export enum MessageId {
|
||||
BatteryLow = 'battery.low',
|
||||
BatteryOk = 'battery.ok',
|
||||
HubInfoConnectedTo = 'hubInfo.connectedTo',
|
||||
HubInfoHubType = 'hubInfo.hubType',
|
||||
HubInfoFirmware = 'hubInfo.firmware',
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
background-color: $pb-pybricks-blue;
|
||||
height: $pb-status-bar-height;
|
||||
width: 100vw;
|
||||
padding-inline: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -19,3 +20,41 @@
|
||||
.pb-status-bar :first-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.pb-battery-indicator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.pb-battery-indicator-body {
|
||||
border-radius: 3px;
|
||||
border: 2px solid $pt-text-color;
|
||||
background-color: $pt-text-color;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.pb-battery-indicator-body .#{$ns}-progress-bar {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.pb-battery-indicator-body .#{$ns}-progress-bar,
|
||||
.pb-battery-indicator-body .#{$ns}-progress-meter {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.pb-battery-indicator-tip {
|
||||
border-top-right-radius: 1px;
|
||||
border-bottom-right-radius: 1px;
|
||||
border: 1px solid $pt-text-color;
|
||||
background-color: $pt-text-color;
|
||||
width: 3px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.#{$ns}-dark .pb-battery-indicator-body,
|
||||
.#{$ns}-dark .pb-battery-indicator-tip {
|
||||
border-color: $pt-dark-text-color;
|
||||
background-color: $pt-dark-text-color;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Menu, MenuDivider, MenuItem, ResizeSensor } from '@blueprintjs/core';
|
||||
import { ContextMenu2, ContextMenu2ContentProps } from '@blueprintjs/popover2';
|
||||
import { useI18n } from '@shopify/react-i18n';
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Terminal as XTerm } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { RootState } from '../reducers';
|
||||
import { useDispatch } from '../actions';
|
||||
import { useSelector } from '../reducers';
|
||||
import { isMacOS } from '../utils/os';
|
||||
import { TerminalContext } from './TerminalContext';
|
||||
import { receiveData } from './actions';
|
||||
@@ -100,7 +100,7 @@ function createContextMenu(
|
||||
const Terminal: React.FC = (_props) => {
|
||||
const { xterm, fitAddon } = useMemo(createXTerm, [createXTerm]);
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
const darkMode = useSelector((state: RootState) => state.settings.darkMode);
|
||||
const darkMode = useSelector((s) => s.settings.darkMode);
|
||||
const dispatch = useDispatch();
|
||||
const terminalStream = useContext(TerminalContext);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
didFailToWrite,
|
||||
didNotify,
|
||||
didWrite,
|
||||
} from '../ble-uart/actions';
|
||||
} from '../ble-nordic-uart-service/actions';
|
||||
import { HubChecksumMessageAction, HubMessageActionType } from '../hub/actions';
|
||||
import { HubRuntimeState } from '../hub/reducers';
|
||||
import { createCountFunc } from '../utils/iter';
|
||||
|
||||
@@ -13,8 +13,12 @@ import {
|
||||
takeEvery,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import { Action } from '../actions';
|
||||
import { BleUartActionType, BleUartDidNotifyAction, write } from '../ble-uart/actions';
|
||||
import { SafeTxCharLength } from '../ble-uart/protocol';
|
||||
import {
|
||||
BleUartActionType,
|
||||
BleUartDidNotifyAction,
|
||||
write,
|
||||
} from '../ble-nordic-uart-service/actions';
|
||||
import { SafeTxCharLength } from '../ble-nordic-uart-service/protocol';
|
||||
import { checksum } from '../hub/actions';
|
||||
import { HubRuntimeState } from '../hub/reducers';
|
||||
import { RootState } from '../reducers';
|
||||
|
||||
Reference in New Issue
Block a user