mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
status-bar: add basic battery indicator
This adds a basic battery indicator that just shows green for OK and red for low battery warning. This is all we can do for now since that is the only information that the hub sends currently. Fixes: https://github.com/pybricks/support/issues/559
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// 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';
|
||||
@@ -13,6 +13,11 @@ 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);
|
||||
@@ -24,9 +29,7 @@ const HubInfoButton: React.VFC = (_props) => {
|
||||
|
||||
return (
|
||||
<Popover2
|
||||
popoverClassName={Classes2.POPOVER2_CONTENT_SIZING}
|
||||
rootBoundary="document"
|
||||
placement="top"
|
||||
{...commonPopoverProps}
|
||||
content={
|
||||
<table className="no-wrap">
|
||||
<tbody>
|
||||
@@ -65,6 +68,39 @@ const HubInfoButton: React.VFC = (_props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const BatteryIndicator: React.VFC = (_props) => {
|
||||
const charging = useSelector((state: RootState) => state.ble.deviceBatteryCharging);
|
||||
const lowBatteryWarning = useSelector(
|
||||
(state: RootState) => state.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);
|
||||
|
||||
@@ -75,7 +111,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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user