From 10b4268ae43c2e1c7dd9662f201f07038153ee4a Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 09:58:54 +0200 Subject: [PATCH 01/21] Open melody editor in synced state when all the melodies are the same. Resolves #91 --- src/Components/MelodyEditor/index.jsx | 3 ++- src/changelog.json | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Components/MelodyEditor/index.jsx b/src/Components/MelodyEditor/index.jsx index 1ec32ac3c..c411a3739 100644 --- a/src/Components/MelodyEditor/index.jsx +++ b/src/Components/MelodyEditor/index.jsx @@ -20,10 +20,11 @@ function MelodyEditor({ }) { const defaultAccepted = melodies.map(() => null); const references = melodies.map(() => useRef()); + const uniqueMelodies = [...new Set(melodies)]; const { t } = useTranslation(); const [allAccepted, setAllAccepted] = useState(false); - const [sync, setSync] = useState(false); + const [sync, setSync] = useState(uniqueMelodies.length <= 1); const [currentMelodies, setCurrentMelodies] = useState(melodies); const [acceptedMelodies, setAcceptedMelodies] = useState(defaultAccepted); const [isAnyPlaying, setIsAnyPlaying] = useState(false); diff --git a/src/changelog.json b/src/changelog.json index 7a8c08164..a859c5a9e 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -1,4 +1,10 @@ [ + { + "title": "Unpublished", + "items": [ + "Show synced screen when all melodies are the same" + ] + }, { "title": "0.11.0", "items": [ From 24ef2f85acdd064ea2d91afb6ebc10209ad1e97b Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 10:37:22 +0200 Subject: [PATCH 02/21] Updated test --- .../MelodyEditor/__tests__/index.test.jsx | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/Components/MelodyEditor/__tests__/index.test.jsx b/src/Components/MelodyEditor/__tests__/index.test.jsx index 1806ec645..9b5406501 100644 --- a/src/Components/MelodyEditor/__tests__/index.test.jsx +++ b/src/Components/MelodyEditor/__tests__/index.test.jsx @@ -23,15 +23,10 @@ test('loads and displays MelodyEditor without melodies', () => { /> ); - expect(screen.getByText(/ESC 1/i)).toBeInTheDocument(); - expect(screen.getByText(/ESC 2/i)).toBeInTheDocument(); - expect(screen.getByText(/ESC 3/i)).toBeInTheDocument(); - expect(screen.getByText(/ESC 4/i)).toBeInTheDocument(); - expect(screen.getAllByText('common:melodyEditorPlay').length).toEqual(4); - expect(screen.getByText('common:melodyEditorPlayAll')).toBeInTheDocument(); - expect(screen.getAllByText(/common:melodyEditorAccept/i).length).toEqual(4); + expect(screen.getAllByText('common:melodyEditorPlay').length).toEqual(1); + expect(screen.getAllByText(/common:melodyEditorAccept/i).length).toEqual(1); expect(screen.queryByText(/common:melodyEditorStop/i)).not.toBeInTheDocument(); - expect(screen.getAllByText(/Please supply a value and an onChange parameter./i).length).toEqual(8); + expect(screen.getAllByText(/Please supply a value and an onChange parameter./i).length).toEqual(2); expect(screen.getByText(/close/i)).toBeInTheDocument(); expect(screen.getByText(/save/i)).toBeInTheDocument(); @@ -42,12 +37,12 @@ test('loads and displays MelodyEditor without melodies', () => { expect(onClose).toHaveBeenCalled(); }); -test('loads and displays MelodyEditor with melodies', () => { +test('loads and displays MelodyEditor with different melodies', () => { const onClose = jest.fn(); const onSave = jest.fn(); const melody = "simpsons:d=4,o=5,b=160:c.6, e6, f#6, 8a6, g.6, e6, c6, 8a, 8f#, 8f#, 8f#, 2g, 8p, 8p, 8f#, 8f#, 8f#, 8g, a#., 8c6, 8c6, 8c6, c6"; - const melodies = [melody, melody, melody, melody]; + const melodies = [melody, melody, melody, `${melody}, f#6`]; render( { expect(onClose).toHaveBeenCalled(); }); -test('loads and displays MelodyEditor with melodies play all', () => { +test('loads and displays MelodyEditor with different play all', () => { const onClose = jest.fn(); const onSave = jest.fn(); const melody = "simpsons:d=4,o=5,b=160:c.6, e6, f#6, 8a6, g.6, e6, c6, 8a, 8f#, 8f#, 8f#, 2g, 8p, 8p, 8f#, 8f#, 8f#, 8g, a#., 8c6, 8c6, 8c6, c6"; - const melodies = [melody, melody, melody, melody]; + const melodies = [melody, melody, melody, `${melody}, f#6`]; render( { const onSave = jest.fn(); const melody = "simpsons:d=4,o=5,b=160:c.6, e6, f#6, 8a6, g.6, e6, c6, 8a, 8f#, 8f#, 8f#, 2g, 8p, 8p, 8f#, 8f#, 8f#, 8g, a#., 8c6, 8c6, 8c6, c6"; - const melodies = [melody, melody, melody, melody]; + const melodies = [melody, melody, melody, `${melody}, f#6`]; render( { melodies={melodies} onClose={onClose} onSave={onSave} + writing={false} /> ); - expect(screen.getByText(/ESC 1/i)).toBeInTheDocument(); - expect(screen.getByText(/ESC 2/i)).toBeInTheDocument(); - expect(screen.getByText(/ESC 3/i)).toBeInTheDocument(); - expect(screen.getByText(/ESC 4/i)).toBeInTheDocument(); - expect(screen.getAllByText('common:melodyEditorPlay').length).toEqual(4); - expect(screen.getByText('common:melodyEditorPlayAll')).toBeInTheDocument(); - expect(screen.getAllByText(/common:melodyEditorAccept/i).length).toEqual(4); + expect(screen.getByText(/common:allEscs/i)).toBeInTheDocument(); + expect(screen.getAllByText('common:melodyEditorPlay').length).toEqual(1); + expect(screen.getAllByText(/common:melodyEditorAccept/i).length).toEqual(1); expect(screen.queryByText(/common:melodyEditorStop/i)).not.toBeInTheDocument(); expect(screen.queryByText(/Please supply a value and an onChange parameter./i)).not.toBeInTheDocument(); expect(screen.getByText(/close/i)).toBeInTheDocument(); @@ -189,12 +181,18 @@ test('loads and displays MelodyEditor with synced', () => { expect(screen.getByRole(/checkbox/i)).toBeInTheDocument(); userEvent.click(screen.getByRole(/checkbox/i)); - expect(screen.getByText(/common:allEscs/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 1/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 2/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 3/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 4/i)).toBeInTheDocument(); userEvent.click(screen.getByText(/common:melodyEditorSave/i)); expect(onSave).not.toHaveBeenCalled(); - userEvent.click(screen.getByText(/common:melodyEditorAccept/i)); + const acceptButtons = screen.getAllByText(/common:melodyEditorAccept/i); + for(let i = 0; i < acceptButtons.length; i += 1) { + userEvent.click(acceptButtons[i]); + } userEvent.click(screen.getByText(/common:melodyEditorSave/i)); expect(onSave).toHaveBeenCalled(); From d057256b803210acc5225a172ee92248409dad12 Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 12:16:52 +0200 Subject: [PATCH 03/21] Add info box when reported ESC count does not match shown ESC count. Resolves #90 --- src/Components/Flash/__tests__/index.test.jsx | 104 +++++++++++++++++- src/Components/Flash/index.jsx | 58 +++++++--- src/Components/Flash/style.scss | 29 +++-- .../MainContent/__tests__/index.test.jsx | 60 ++++++++++ src/Components/MainContent/index.jsx | 75 ++++++++++--- src/Components/MainContent/style.scss | 12 ++ src/changelog.json | 3 +- src/translations/en/common.json | 8 +- 8 files changed, 301 insertions(+), 48 deletions(-) diff --git a/src/Components/Flash/__tests__/index.test.jsx b/src/Components/Flash/__tests__/index.test.jsx index e5034d9b1..b060db228 100644 --- a/src/Components/Flash/__tests__/index.test.jsx +++ b/src/Components/Flash/__tests__/index.test.jsx @@ -110,14 +110,114 @@ test('loads and displays unsupported CustomSettings', () => { + ); + + expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); + expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); +}); + + +test('displays missing ESC warning', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const escs = [ + { + index: 0, + meta: { available: true }, + settings: { MODE: 0 }, + make: 'make 1234', + settingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }, + ]; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + ); - expect(screen.getByText(/notePropsOff/i)).toBeInTheDocument(); - expect(screen.getByText(/noteConnectPower/i)).toBeInTheDocument(); expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); }); diff --git a/src/Components/Flash/index.jsx b/src/Components/Flash/index.jsx index 998bbc9b8..3605c5871 100644 --- a/src/Components/Flash/index.jsx +++ b/src/Components/Flash/index.jsx @@ -7,15 +7,11 @@ import Escs from './Escs'; import './style.scss'; -/** - * @param {Object} {escs} Parameters - * - * @return {Component} The Component - */ function Flash({ availableSettings, canFlash, directInput, + escCount, escs, flashProgress, onFlash, @@ -23,18 +19,49 @@ function Flash({ onSettingsUpdate, }) { const { t } = useTranslation('common'); - return ( -
-
-

- -
+ function CountWarning() { + if(escCount !== escs.length) { + return ( +

+
+
+ {t('escMissingHeader')} +
+
- -

-
+
+

+ {t('escMissingText')} +

+ +
    +
  • + +
  • + +
  • +
+

+

+
+ ); + } + + return null; + } + + return ( +
{escs.length > 0 && @@ -56,6 +83,8 @@ function Flash({ onFlash={onFlash} onSettingsUpdate={onIndividualSettingsUpdate} /> + +
@@ -71,6 +100,7 @@ Flash.propTypes = { availableSettings: PropTypes.shape().isRequired, canFlash: PropTypes.bool, directInput: PropTypes.bool, + escCount: PropTypes.number.isRequired, escs: PropTypes.arrayOf(PropTypes.shape()).isRequired, flashProgress: PropTypes.arrayOf(PropTypes.number).isRequired, onFlash: PropTypes.func.isRequired, diff --git a/src/Components/Flash/style.scss b/src/Components/Flash/style.scss index f272fb389..3fa3a6357 100644 --- a/src/Components/Flash/style.scss +++ b/src/Components/Flash/style.scss @@ -1,16 +1,4 @@ #flash-content { - .note { - margin-bottom: 20px; - background-color: #fff7cd; - border: 1px solid #ffe55f; - margin-top: 5px; - margin-bottom: 25px; - border-radius: 3px; - font-size: 11px; - font-family: 'open_sansregular', Arial; - padding: 5px 7px 5px 7px; - } - .config-wrapper { display: flex; @@ -37,4 +25,21 @@ } } } + + .missing-esc.gui-box { + p { + padding-bottom: 10px; + font-weight: normal; + } + + ul { + margin-left: 15px; + + li { + list-style-type: disc; + font-weight: normal; + padding-bottom: 5px; + } + } + } } diff --git a/src/Components/MainContent/__tests__/index.test.jsx b/src/Components/MainContent/__tests__/index.test.jsx index 80bf3e6a7..cb38c88f3 100644 --- a/src/Components/MainContent/__tests__/index.test.jsx +++ b/src/Components/MainContent/__tests__/index.test.jsx @@ -827,3 +827,63 @@ test('isSelecting with ESC', () => { expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); }); + +test('fourWay', () => { + const onWriteSetup = jest.fn(); + const onSettingsUpdate = jest.fn(); + const onSingleMotorSpeed = jest.fn(); + const onSingleFlash = jest.fn(); + const onSelectFirmwareForAll = jest.fn(); + const onSaveLog = jest.fn(); + const onResetDefaultls = jest.fn(); + const onReadEscs = jest.fn(); + const onLocalSubmit = jest.fn(); + const onIndividualSettingsUpdate = jest.fn(); + const onFlashUrl = jest.fn(); + const onCancelFirmwareSelection = jest.fn(); + const onAllMotorSpeed = jest.fn(); + const onOpenMelodyEditor = jest.fn(); + + const actions = { + isReading: false, + isWriting: false, + isSelecting: false, + isFlashing: false, + }; + + const configs = { + versions: {}, + escs: {}, + pwm: {}, + platforms: {}, + }; + + render( + + ); + + expect(screen.getByText(/notePropsOff/i)).toBeInTheDocument(); + expect(screen.getByText(/noteConnectPower/i)).toBeInTheDocument(); + expect(screen.queryByText('motorControl')).not.toBeInTheDocument(); + expect(screen.getByText("escButtonSaveLog")).toBeInTheDocument(); + expect(screen.getByText(/escButtonFlashAll/i)).toBeInTheDocument(); +}); diff --git a/src/Components/MainContent/index.jsx b/src/Components/MainContent/index.jsx index 8be9bdf08..610fbc25b 100644 --- a/src/Components/MainContent/index.jsx +++ b/src/Components/MainContent/index.jsx @@ -1,10 +1,11 @@ -import React from 'react'; +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; +import React from 'react'; import Home from '../Home'; import Flash from '../Flash'; import Buttonbar from '../Buttonbar'; -import FirmwareSelector from '..//FirmwareSelector'; +import FirmwareSelector from '../FirmwareSelector'; import Changelog from '../../Components/Changelog'; import MotorControl from '../../Components/MotorControl'; @@ -37,6 +38,8 @@ function MainContent({ connected, fourWay, }) { + const { t } = useTranslation('common'); + const { isSelecting, isFlashing, @@ -60,6 +63,54 @@ function MainContent({ ); } + function FlashWrapper() { + if(fourWay) { + return ( + + ); + } + + return null; + } + + function MotorControlWrapper() { + if(!fourWay && !actions.isReading) { + return ( + + ); + } + + return null; + } + + function WarningWrapper() { + return ( +
+

+ + +
+ + +

+
+ ); + } + if (isSelecting) { const targetIndex = flashTargets[0]; const esc = escs.find((esc) => esc.index === targetIndex); @@ -86,23 +137,11 @@ function MainContent({
- + + + - {!fourWay && !actions.isReading && - } +
diff --git a/src/Components/MainContent/style.scss b/src/Components/MainContent/style.scss index c7cc85179..7bbad0644 100644 --- a/src/Components/MainContent/style.scss +++ b/src/Components/MainContent/style.scss @@ -10,6 +10,18 @@ -webkit-transform: rotateX(0deg); /* DO NOT REMOVE! this fixes the UI freezing bug on MAC OS X */ transition: all 0.3s; + .note { + margin-bottom: 20px; + background-color: #fff7cd; + border: 1px solid #ffe55f; + margin-top: 5px; + margin-bottom: 25px; + border-radius: 3px; + font-size: 11px; + font-family: 'open_sansregular', Arial; + padding: 5px 7px 5px 7px; + } + @media only screen and (max-width: 600px) { padding-top: 170px } diff --git a/src/changelog.json b/src/changelog.json index a859c5a9e..c69ec2706 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -2,7 +2,8 @@ { "title": "Unpublished", "items": [ - "Show synced screen when all melodies are the same" + "Show synced screen when all melodies are the same", + "Show info when esc connected count does not match what MSP is reporting" ] }, { diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 5e84c09b1..52d7b15c6 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -114,5 +114,11 @@ "settings": "Settings", "openMelodyEditor": "Open Melody Editor", "addToHomeScreen": "Add to Homescreen", - "homeInstall": "

ESC-Configurator can also be used offline when added to the homescreen. Once flashed, firmware files are available offline.

Settings can always be adjusted when offline.

" + "homeInstall": "

ESC-Configurator can also be used offline when added to the homescreen. Once flashed, firmware files are available offline.

Settings can always be adjusted when offline.

", + "escMissingHeader": "Missing an ESC?", + "escMissingText": "The amount of available ESCs does not match what the flight controller is reporting. This can have multiple reasons:", + "escMissingHint": "If the result is not what you are expecting, try clicking the 'Read Setup' button again.", + "escMissing1": "The ESC was not ready yet. This can for example happen if it is playing a melody. Also a previous flash could have gone wrong and it needs longer to boot up than usual.", + "escMissing2": "There are simply not more ESCs connected.", + "escMissing3": "The MCU of the ESC is defective." } From deb0dca279692dd834bcbbc3d8d96269d489a4c8 Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 17:54:58 +0200 Subject: [PATCH 04/21] Add functionality to check for 3D mode and set sliders accordingly. #77 --- src/Components/App/index.jsx | 3 ++ .../MainContent/__tests__/index.test.jsx | 3 ++ src/Components/MainContent/index.jsx | 4 ++ src/Components/MotorControl/index.jsx | 15 ++++-- src/Containers/App/index.jsx | 6 +++ src/utils/Msp.js | 49 +++++++++++++++++++ src/utils/Serial.js | 1 + 7 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx index b2a98b0b2..eaa3d2457 100644 --- a/src/Components/App/index.jsx +++ b/src/Components/App/index.jsx @@ -24,6 +24,7 @@ function App({ melodies, escs, language, + msp, onAllMotorSpeed, onCookieAccept, onSaveLog, @@ -139,6 +140,7 @@ function App({ escs={escs.individual} flashTargets={escs.targets} fourWay={serial.fourWay} + mspFeatures={msp.features} onAllMotorSpeed={onAllMotorSpeed} onCancelFirmwareSelection={escs.actions.handleCancelFirmwareSelection} onFlashUrl={escs.actions.handleFlashUrl} @@ -243,6 +245,7 @@ App.propTypes = { escs: PropTypes.arrayOf(PropTypes.string).isRequired, show: PropTypes.bool.isRequired, }).isRequired, + msp: PropTypes.shape({ features: PropTypes.shape({}).isRequired }).isRequired, onAllMotorSpeed: PropTypes.func.isRequired, onCookieAccept: PropTypes.func.isRequired, onSaveLog: PropTypes.func.isRequired, diff --git a/src/Components/MainContent/__tests__/index.test.jsx b/src/Components/MainContent/__tests__/index.test.jsx index cb38c88f3..f7528a8a1 100644 --- a/src/Components/MainContent/__tests__/index.test.jsx +++ b/src/Components/MainContent/__tests__/index.test.jsx @@ -301,10 +301,13 @@ test('isWriting', () => { platforms: {}, }; + const mspFeatures = { '3D': true }; + render( ); } @@ -170,6 +172,7 @@ MainContent.defaultProps = { escs: [], flashTargets: [], fourWay: false, + mspFeatures: { '3D': false }, open: false, progress: [], settings: {}, @@ -193,6 +196,7 @@ MainContent.propTypes = { escs: PropTypes.arrayOf(PropTypes.shape()), flashTargets: PropTypes.arrayOf(PropTypes.number), fourWay: PropTypes.bool, + mspFeatures: PropTypes.shape({ '3D': PropTypes.bool }), onAllMotorSpeed: PropTypes.func.isRequired, onCancelFirmwareSelection: PropTypes.func.isRequired, onFlashUrl: PropTypes.func.isRequired, diff --git a/src/Components/MotorControl/index.jsx b/src/Components/MotorControl/index.jsx index 4d00a0b07..dd429438b 100644 --- a/src/Components/MotorControl/index.jsx +++ b/src/Components/MotorControl/index.jsx @@ -17,6 +17,7 @@ function MotorControl({ motorCount, onAllUpdate, onSingleUpdate, + startValue, }) { const { t } = useTranslation('common'); @@ -28,18 +29,18 @@ function MotorControl({ function toggleUnlock() { setUnlock(!unlock); - onAllUpdate(minValue); + onAllUpdate(startValue); } // Makes no sense to test, component has its own test, we just assume that // the slider actually slides. /* istanbul ignore next */ function updateValue(value) { - if(value > minValue && unlockIndividual) { + if(value !== startValue && unlockIndividual) { setUnlockIndividual(false); } - if(value === minValue) { + if(value === startValue) { setUnlockIndividual(true); } @@ -55,7 +56,7 @@ function MotorControl({ disabled, onChange, }) { - const [value, setValue] = useState(minValue); + const [value, setValue] = useState(startValue); /* istanbul ignore next */ function update(value) { setValue(value); @@ -171,12 +172,16 @@ function MotorControl({ ); } -MotorControl.defaultProps = { motorCount: 0 }; +MotorControl.defaultProps = { + motorCount: 0, + startValue: 1000, +}; MotorControl.propTypes = { motorCount: PropTypes.number, onAllUpdate: PropTypes.func.isRequired, onSingleUpdate: PropTypes.func.isRequired, + startValue: PropTypes.number, }; export default MotorControl; diff --git a/src/Containers/App/index.jsx b/src/Containers/App/index.jsx index 455895c95..5877980a6 100644 --- a/src/Containers/App/index.jsx +++ b/src/Containers/App/index.jsx @@ -115,6 +115,7 @@ class App extends Component { this.lastConnected = 0; this.state = { + msp: { features: {} }, appSettings: { show: false, settings: loadSettings(), @@ -773,6 +774,8 @@ class App extends Component { let motorData = await this.serial.getMotorData(); motorData = motorData.filter((motor) => motor > 0); + const features = await this.serial.getFeatures(); + TagManager.dataLayer({ dataLayer: { event: "FlightController", @@ -785,6 +788,7 @@ class App extends Component { }, }); + this.setState({ msp: { features } }); this.setSerial({ open: true }); this.setEscs({ connected: motorData.length }); } catch(e) { @@ -931,6 +935,7 @@ class App extends Component { configs, language, melodies, + msp, serial, stats, appSettings, @@ -981,6 +986,7 @@ class App extends Component { }, ...melodies, }} + msp={msp} onAllMotorSpeed={this.handleAllMotorSpeed} onCookieAccept={this.handleCookieAccept} onSaveLog={this.handleSaveLog} diff --git a/src/utils/Msp.js b/src/utils/Msp.js index 6585ee26c..30a22f1a9 100644 --- a/src/utils/Msp.js +++ b/src/utils/Msp.js @@ -9,6 +9,9 @@ const MSP = { MSP_BOARD_INFO: 4, MSP_BUILD_INFO: 5, + MSP_FEATURE_CONFIG: 36, + MSP_MOTOR_3D_CONFIG: 124, + MSP_BATTERY_STATE: 130, MSP_SET_MOTOR: 214, @@ -28,6 +31,30 @@ const MSP = { MSP2_SEND_DSHOT_COMMAND: 0x3003, }; +/* Features that are valid across all MSP versions. + * Basically only used to check if 3D mode is enabled. + */ +const FEATURES = [ + 'RX_PPM', + null, + 'INFLIGHT_ACC_CAL', + 'RX_SERIAL', + 'MOTOR_STOP', + 'SERVO_TILT', + 'SOFTSERIAL', + 'GPS', + null, + 'SONAR', + 'TELEMETRY', + null, + '3D', + 'RX_PARALLEL_PWM', + 'RX_MSP', + 'RSSI_ADC', + 'LED_STRIP', + 'DISPLAY', +]; + class Msp { constructor(serial) { this.serial = serial; @@ -297,6 +324,10 @@ class Msp { return this.send(MSP.MSP_BATTERY_STATE); } + getFeatures() { + return this.send(MSP.MSP_FEATURE_CONFIG); + } + set4WayIf() { return this.send(MSP.MSP_SET_PASSTHROUGH); } @@ -461,6 +492,24 @@ class Msp { return config; } + case MSP.MSP_FEATURE_CONFIG: { + const featureBits = data.getUint32(0, 1); + const features = {}; + FEATURES.map((key, index) => { + if(key) { + const mask = 1 << index; + const item = { + key, + enabled: (featureBits & mask) !== 0, + }; + + features[key] = item.enabled; + } + }); + + return features; + } + case MSP.MSP_BATTERY_STATE: { const battery = { cellCount: data.getUint8(0), diff --git a/src/utils/Serial.js b/src/utils/Serial.js index 2924cc571..04202cbeb 100644 --- a/src/utils/Serial.js +++ b/src/utils/Serial.js @@ -47,6 +47,7 @@ class Serial { getBuildInfo = () => this.msp.getBuildInfo(); getFcVariant = () => this.msp.getFcVariant(); getFcVersion = () => this.msp.getFcVersion(); + getFeatures = () => this.msp.getFeatures(); getMotorData = () => this.msp.getMotorData(); getUid = () => this.msp.getUid(); spinAllMotors = (speed) => this.msp.spinAllMotors(speed); From 59351377bc231cc8d3d2dcf1691f3ded4868899a Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 17:55:33 +0200 Subject: [PATCH 05/21] Fixed linting errors --- src/Components/Flash/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Flash/index.jsx b/src/Components/Flash/index.jsx index 3605c5871..f736d183b 100644 --- a/src/Components/Flash/index.jsx +++ b/src/Components/Flash/index.jsx @@ -50,7 +50,7 @@ function Flash({

From 169799b03988440a55eb58e77fe45cc192f49a99 Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 19:16:46 +0200 Subject: [PATCH 06/21] Add some information text for the motor sliders. #77 --- src/Components/MotorControl/index.jsx | 4 ++++ src/Components/MotorControl/style.scss | 7 +++++++ src/translations/en/common.json | 1 + 3 files changed, 12 insertions(+) diff --git a/src/Components/MotorControl/index.jsx b/src/Components/MotorControl/index.jsx index dd429438b..9842413d0 100644 --- a/src/Components/MotorControl/index.jsx +++ b/src/Components/MotorControl/index.jsx @@ -144,6 +144,10 @@ function MotorControl({
+
+

Let us know by opening an issue.", "motorControl": "Motor Control", + "motorControlText": "

Make your ESCs are properly set up to reflect the state of the sliders.

Eg.: When you have 3D mode enabled, make sure the ESCs are also set up for 3D mode, otherwise the motors might go off with full power.

Also be aware that the motors will not spin if you have bi-directional Dshot enabled on the Flight-controller, but not on the ESC. Which might be the case when flashing from RPM emabled firmware to BlHeli_S.

", "enableMotorControl": "Enable motor control", "masterSpeed": "Master Speed", "motorNr": "Motor {{index}}", From de2b4394014cf9e8bd2c60c176edfff89e28e963 Mon Sep 17 00:00:00 2001 From: Chris L Date: Fri, 9 Apr 2021 08:54:11 +0200 Subject: [PATCH 07/21] Updated RTTTL lib. Resolves #93 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 72ec901b9..4743ff1bf 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", - "bluejay-rtttl-parse": "^2.0.1", + "bluejay-rtttl-parse": "^2.0.2", "compare-versions": "^3.6.0", "dateformat": "^4.5.1", "i18next": "^19.9.0", diff --git a/yarn.lock b/yarn.lock index 09596eb5e..d888f9502 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2823,10 +2823,10 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bluejay-rtttl-parse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/bluejay-rtttl-parse/-/bluejay-rtttl-parse-2.0.1.tgz#ab0ac4c16e457a5e4a30c86e9288a57709ad89e6" - integrity sha512-iwX0NVPnL2K7wteuAOLyshcVb+Vlew4nbgWhfx7N5FbRjeKIUz0CTSAMO8uKl4IGV5qYAWnjEg66CKikQtnnrg== +bluejay-rtttl-parse@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bluejay-rtttl-parse/-/bluejay-rtttl-parse-2.0.2.tgz#44111849591a22aac315b5791400b72fb02b88e5" + integrity sha512-Rs6eGqbCjSM4n9V93+tC6b9xZMCkTF67aRkfFzaVFlrFPJo8Gxi0swLisdUL4n4bBNfgkGEVzkEPww9L60gMng== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.11.9" From 9a18e5ee9fddbc6cd717006733b8901bf4e365c6 Mon Sep 17 00:00:00 2001 From: Chris L Date: Thu, 8 Apr 2021 22:02:43 +0200 Subject: [PATCH 08/21] Add functionality to select melody from presets. #89 --- src/Components/App/index.jsx | 2 + src/Components/Input/LabeledSelect/index.jsx | 4 +- .../MelodyEditor/__tests__/index.test.jsx | 33 +++++++++++++ src/Components/MelodyEditor/index.jsx | 48 ++++++++++++++++++- src/Containers/App/index.jsx | 3 ++ src/melodies.json | 39 +++++++++++++++ src/translations/en/common.json | 1 + 7 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 src/melodies.json diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx index eaa3d2457..a364fd29e 100644 --- a/src/Components/App/index.jsx +++ b/src/Components/App/index.jsx @@ -179,6 +179,7 @@ function App({ melodies={melodies.escs} onClose={melodies.actions.handleClose} onSave={melodies.actions.handleSave} + presets={melodies.melodies} writing={actions.isWriting} />} @@ -243,6 +244,7 @@ App.propTypes = { }), dummy: PropTypes.bool.isRequired, escs: PropTypes.arrayOf(PropTypes.string).isRequired, + melodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, show: PropTypes.bool.isRequired, }).isRequired, msp: PropTypes.shape({ features: PropTypes.shape({}).isRequired }).isRequired, diff --git a/src/Components/Input/LabeledSelect/index.jsx b/src/Components/Input/LabeledSelect/index.jsx index 2b88714c5..4c62dc42c 100644 --- a/src/Components/Input/LabeledSelect/index.jsx +++ b/src/Components/Input/LabeledSelect/index.jsx @@ -20,7 +20,7 @@ function LabeledSelect({ return ( + +
+ + +
+
+ ); +} +SaveMelody.propTypes = { onSave: PropTypes.func.isRequired }; + +function PresetSelect({ + escs, + onUpdateMelodies, + presets, +}) { + const { t } = useTranslation(); + + const [selectedPreset, setSelectedPreset] = useState(-1); + + + function handleUpdate(e) { + const value = e.target.value; + const selected = JSON.parse(value); + + setSelectedPreset(value); + onUpdateMelodies(selected); + } + + const possibilities = presets.filter((item) => item.tracks.length <= escs ); + const options = possibilities.map((melody) => { + if(melody.tracks.length <= escs) { + return { + key: melody.name, + name: melody.name, + value: JSON.stringify(melody.tracks), + }; + } + }); + + return( + + ); +} +PresetSelect.propTypes = { + escs: PropTypes.number.isRequired, + onUpdateMelodies: PropTypes.func.isRequired, + presets: PropTypes.arrayOf(PropTypes.shape({})).isRequired, +}; + function MelodyEditor({ dummy, melodies, @@ -30,7 +110,6 @@ function MelodyEditor({ const [currentMelodies, setCurrentMelodies] = useState(melodies); const [acceptedMelodies, setAcceptedMelodies] = useState(defaultAccepted); const [isAnyPlaying, setIsAnyPlaying] = useState(false); - const [selectedPreset, setSelectedPreset] = useState(-1); const totalPlaying = useRef(0); const audioContext = useRef(0); @@ -113,61 +192,34 @@ function MelodyEditor({ setSync(!sync); } - function updateMelodies(e) { - const value = e.target.value; - const selected = JSON.parse(value); - const newMelodies = []; + function handleSaveMelody(name) { + console.log('save melodies', name); + } + function handleMelodiesUpdate(selected) { + const newMelodies = []; let currentTrack = 0; while(newMelodies.length < melodies.length) { newMelodies[currentTrack] = selected[currentTrack % selected.length]; currentTrack += 1; } - setSelectedPreset(value); setSync(selected.length === 1); setCurrentMelodies(newMelodies); } - function PresetSelect() { - const possibilities = presets.filter((item) => item.tracks.length <= melodies.length ); - const options = possibilities.map((melody) => { - if(melody.tracks.length <= melodies.length) { - return { - key: melody.name, - name: melody.name, - value: JSON.stringify(melody.tracks), - }; - } - }); - - return( - - ); - } - - function MelodyElementSingle({ - accepted, - index, - label, - melody, - onAccept, - }) { + const melodyElements = currentMelodies.map((melody, index) => { function handleAcceptMelody(accept) { - onAccept(index, accept); + handleAccept(index, accept); } - return( + return ( ); - } - MelodyElementSingle.propTypes = { - accepted: PropTypes.bool.isRequired, - index: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - melody: PropTypes.string.isRequired, - onAccept: PropTypes.func.isRequired, - }; - - function MelodyElementAll({ - accepted, - label, - melody, - }) { - return( - - ); - } - MelodyElementAll.propTypes = { - accepted: PropTypes.bool.isRequired, - label: PropTypes.string.isRequired, - melody: PropTypes.string.isRequired, - }; - - const melodyElements = useMemo( - () => currentMelodies.map((melody, index) => ( - - )), [currentMelodies, writing] - ); - - const melodyElement = useMemo( - () => ( - - ), [currentMelodies, writing] - ); + }); return (
- + + +
{!sync && melodyElements} - {sync && melodyElement} + {sync && + }
@@ -286,7 +302,7 @@ function MelodyEditor({ onClick={handleSave} type="button" > - {t('common:melodyEditorSave')} + {t('common:melodyEditorWrite')} }
diff --git a/src/Components/MelodyEditor/style.scss b/src/Components/MelodyEditor/style.scss index 7cc1e5f48..02c8b7a2c 100644 --- a/src/Components/MelodyEditor/style.scss +++ b/src/Components/MelodyEditor/style.scss @@ -10,6 +10,20 @@ justify-content: center; align-items: center; + .save-melody-wrapper { + display: flex; + flex-direction: row; + + input { + width: 210px; + } + + button { + margin-left: 10px; + width: 200px; + } + } + .melody-editor-wrapper { padding: 10px; background: white; diff --git a/src/changelog.json b/src/changelog.json index c69ec2706..994a2c35c 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -3,7 +3,9 @@ "title": "Unpublished", "items": [ "Show synced screen when all melodies are the same", - "Show info when esc connected count does not match what MSP is reporting" + "Show info when esc connected count does not match what MSP is reporting", + "Melody preset selection", + "Save melodies for later" ] }, { diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 708fb75d9..c24908a69 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -92,7 +92,9 @@ "homeChinaHeader": "For our Chinese visitors", "homeChinaText": "Tell your friends behind the great firewall of China, that they can reach us via a local mirror directly in China.", "melodyEditorHeader": "Melody Editor", + "melodyEditorWrite": "Write Melodies", "melodyEditorSave": "Save", + "melodyEditorName": "Melody name", "melodyEditorPlay": "Play", "melodyEditorStop": "Stop", "melodyEditorAccept": "Accept", From 9881bcce773657313e7332f0fa79b4b4e919627f Mon Sep 17 00:00:00 2001 From: Chris L Date: Fri, 9 Apr 2021 14:02:50 +0200 Subject: [PATCH 10/21] Add functionality to save custom melodies to local storage. #89 --- src/Components/App/index.jsx | 8 +- src/Components/Input/LabeledSelect/index.jsx | 2 + .../MelodyElement/__tests__/index.test.jsx | 10 ++ .../MelodyEditor/MelodyElement/index.jsx | 5 + .../MelodyEditor/__tests__/index.test.jsx | 93 ++++++++++++++----- src/Components/MelodyEditor/index.jsx | 79 ++++++++++++---- src/Components/MelodyEditor/style.scss | 4 +- src/Containers/App/index.jsx | 35 ++++++- src/translations/en/common.json | 1 + 9 files changed, 194 insertions(+), 43 deletions(-) diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx index a364fd29e..3d19c345f 100644 --- a/src/Components/App/index.jsx +++ b/src/Components/App/index.jsx @@ -175,11 +175,13 @@ function App({ {melodies.show && } @@ -240,11 +242,13 @@ App.propTypes = { actions: PropTypes.shape({ handleSave: PropTypes.func.isRequired, handleOpen: PropTypes.func.isRequired, + handleWrite: PropTypes.func.isRequired, handleClose: PropTypes.func.isRequired, }), + customMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + defaultMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, dummy: PropTypes.bool.isRequired, escs: PropTypes.arrayOf(PropTypes.string).isRequired, - melodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, show: PropTypes.bool.isRequired, }).isRequired, msp: PropTypes.shape({ features: PropTypes.shape({}).isRequired }).isRequired, diff --git a/src/Components/Input/LabeledSelect/index.jsx b/src/Components/Input/LabeledSelect/index.jsx index 4c62dc42c..16bed8742 100644 --- a/src/Components/Input/LabeledSelect/index.jsx +++ b/src/Components/Input/LabeledSelect/index.jsx @@ -11,6 +11,7 @@ function LabeledSelect({ function Select() { const optionElements = options.map((item) => (
@@ -283,6 +325,7 @@ function MelodyEditor({ onAccept={handleAcceptAll} onPlay={handlePlay} onStop={handleStop} + onUpdate={handleMelodiesUpdateAll} />}
@@ -311,11 +354,13 @@ function MelodyEditor({ } MelodyEditor.propTypes = { + customMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + defaultMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, dummy: PropTypes.bool.isRequired, melodies: PropTypes.arrayOf(PropTypes.string).isRequired, onClose: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, - presets: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + onWrite: PropTypes.func.isRequired, writing: PropTypes.bool.isRequired, }; diff --git a/src/Components/MelodyEditor/style.scss b/src/Components/MelodyEditor/style.scss index 02c8b7a2c..dee4b4032 100644 --- a/src/Components/MelodyEditor/style.scss +++ b/src/Components/MelodyEditor/style.scss @@ -15,12 +15,14 @@ flex-direction: row; input { + border: 1px solid silver; + padding: 5px; width: 210px; } button { margin-left: 10px; - width: 200px; + width: 100px; } } diff --git a/src/Containers/App/index.jsx b/src/Containers/App/index.jsx index f8ca535c5..25a28322d 100644 --- a/src/Containers/App/index.jsx +++ b/src/Containers/App/index.jsx @@ -94,6 +94,15 @@ class App extends Component { return null; }; + this.loadMelodies = () => { + const storedMelodies = JSON.parse(localStorage.getItem('melodies')); + if(storedMelodies) { + return storedMelodies; + } + + return []; + }; + this.languages = [ { label: "English", @@ -167,7 +176,8 @@ class App extends Component { ], show: false, dummy: true, - melodies, + defaultMelodies: melodies, + customMelodies: this.loadMelodies(), }, }; @@ -891,7 +901,27 @@ class App extends Component { }); } - handleMelodySave = (melodies) => { + handleMelodySave = (name, tracks) => { + const storedMelodies = JSON.parse(localStorage.getItem('melodies')) || []; + const match = storedMelodies.findIndex((melody) => melody.name === name); + + // Override melody if a custom melody with this name is available. + if(match >= 0) { + storedMelodies[match].tracks = tracks; + } else { + storedMelodies.push( + { + name, + tracks, + } + ); + } + + localStorage.setItem('melodies', JSON.stringify(storedMelodies)); + this.setMelodies({ customMelodies: this.loadMelodies() }); + } + + handleMelodyWrite = (melodies) => { const { escs } = this.state; const individual = [ ...escs.individual ]; const converted = melodies.map((melody) => Rtttl.toBluejayStartupMelody(melody)); @@ -984,6 +1014,7 @@ class App extends Component { melodies={{ actions: { handleSave: this.handleMelodySave, + handleWrite: this.handleMelodyWrite, handleOpen: this.handleMelodyEditorOpen, handleClose: this.handleMelodyEditorClose, }, diff --git a/src/translations/en/common.json b/src/translations/en/common.json index c24908a69..204549f84 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -94,6 +94,7 @@ "melodyEditorHeader": "Melody Editor", "melodyEditorWrite": "Write Melodies", "melodyEditorSave": "Save", + "melodyEditorCustom": "Saved melodies", "melodyEditorName": "Melody name", "melodyEditorPlay": "Play", "melodyEditorStop": "Stop", From b1ea15273f0be082d9e54779279c3758f91008a4 Mon Sep 17 00:00:00 2001 From: Chris L Date: Fri, 9 Apr 2021 22:21:57 +0200 Subject: [PATCH 11/21] Allow deletion of custom melodies #89 --- src/Components/App/index.jsx | 2 + src/Components/App/style.scss | 2 +- .../MelodyEditor/__tests__/index.test.jsx | 6 +- src/Components/MelodyEditor/index.jsx | 118 +++++++++++++----- src/Components/MelodyEditor/style.scss | 35 +++++- src/Containers/App/index.jsx | 11 ++ src/changelog.json | 6 +- src/translations/en/common.json | 3 +- 8 files changed, 146 insertions(+), 37 deletions(-) diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx index 3d19c345f..f173bd7a6 100644 --- a/src/Components/App/index.jsx +++ b/src/Components/App/index.jsx @@ -180,6 +180,7 @@ function App({ dummy={melodies.dummy} melodies={melodies.escs} onClose={melodies.actions.handleClose} + onDelete={melodies.actions.handleDelete} onSave={melodies.actions.handleSave} onWrite={melodies.actions.handleWrite} writing={actions.isWriting} @@ -244,6 +245,7 @@ App.propTypes = { handleOpen: PropTypes.func.isRequired, handleWrite: PropTypes.func.isRequired, handleClose: PropTypes.func.isRequired, + handleDelete: PropTypes.func.isRequired, }), customMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, defaultMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, diff --git a/src/Components/App/style.scss b/src/Components/App/style.scss index b846fd7ff..f3138224f 100644 --- a/src/Components/App/style.scss +++ b/src/Components/App/style.scss @@ -215,7 +215,7 @@ body { text-shadow: none; border: 1px solid #ccc; color: #ccc; - cursor: aliasdefault; + cursor: default; } button:active, diff --git a/src/Components/MelodyEditor/__tests__/index.test.jsx b/src/Components/MelodyEditor/__tests__/index.test.jsx index 927726f89..a329f573e 100644 --- a/src/Components/MelodyEditor/__tests__/index.test.jsx +++ b/src/Components/MelodyEditor/__tests__/index.test.jsx @@ -250,7 +250,7 @@ test('update preset', () => { fireEvent.change(screen.getByRole('combobox'), { target: { name: "", - value: "[\"bluejay:b=570,o=4,d=32:4b,p,4e5,p,4b,p,4f#5,2p,4e5,2b5,8b5\"]", + value: "preset-Bluejay Default", }, }); expect(screen.queryAllByText(/bluejay:b=570,o=4,d=32/i).length).toEqual(2); @@ -260,6 +260,7 @@ test('saves melody', () => { const onClose = jest.fn(); const onWrite = jest.fn(); const onSave = jest.fn(); + const onDelete = jest.fn(); const melody = "simpsons:d=4,o=5,b=160:c.6, e6, f#6, 8a6, g.6, e6, c6, 8a, 8f#, 8f#, 8f#, 2g, 8p, 8p, 8f#, 8f#, 8f#, 8g, a#., 8c6, 8c6, 8c6, c6"; const melodies = [melody, melody, melody, melody]; @@ -270,6 +271,7 @@ test('saves melody', () => { defaultMelodies={defaultMelodies} melodies={melodies} onClose={onClose} + onDelete={onDelete} onSave={onSave} onWrite={onWrite} writing={false} @@ -287,4 +289,6 @@ test('saves melody', () => { }); userEvent.click(screen.getByText(/common:melodyEditorSave/i)); expect(onSave).toHaveBeenCalled(); + + userEvent.click(screen.getByText(/melodyDelete/i)); }); diff --git a/src/Components/MelodyEditor/index.jsx b/src/Components/MelodyEditor/index.jsx index 6a91ce963..6843e512d 100644 --- a/src/Components/MelodyEditor/index.jsx +++ b/src/Components/MelodyEditor/index.jsx @@ -50,30 +50,64 @@ SaveMelody.propTypes = { onSave: PropTypes.func.isRequired }; function PresetSelect({ escs, + onDelete, onUpdateMelodies, customMelodies, defaultMelodies, + selected, }) { const { t } = useTranslation('common'); - const [selectedPreset, setSelectedPreset] = useState(-1); + const [selectedPreset, setSelectedPreset] = useState(selected); + const [canDelete, setCanDelete] = useState(false); + useEffect(() => { + setSelectedPreset(selected); + }, [selected]); + + useEffect(() => { + let canDelete = false; + const match = customMelodies.find((item) => item.name === selectedPreset); + if(match) { + canDelete = true; + } + + setCanDelete(canDelete); + }, [selectedPreset]); function handleUpdate(e) { const value = e.target.value; - const selected = JSON.parse(value); + let selected = []; + let match = null; + if(value.startsWith('preset-')) { + const name = value.split('preset-')[1]; + match = defaultMelodies.find((item) => item.name === name); + } else { + match = customMelodies.find((item) => item.name === value); + } - setSelectedPreset(value); + if(match) { + selected = match.tracks; + } + + setSelectedPreset(e.target.value); onUpdateMelodies(selected); } + function handleDelete() { + setSelectedPreset(defaultMelodies[0].name); + onUpdateMelodies(defaultMelodies[0].tracks); + + onDelete(selectedPreset); + } + const defaultPossibilities = defaultMelodies.filter((item) => item.tracks.length <= escs ); const defaultOptions = defaultPossibilities.map((melody) => { if(melody.tracks.length <= escs) { return { - key: melody.name, + key: `preset-${melody.name}`, name: melody.name, - value: JSON.stringify(melody.tracks), + value: `preset-${melody.name}`, }; } }); @@ -84,7 +118,7 @@ function PresetSelect({ return { key: melody.name, name: melody.name, - value: JSON.stringify(melody.tracks), + value: melody.name, }; } }); @@ -100,25 +134,43 @@ function PresetSelect({ ]; return( - +
+ + +
+ +
+
); } PresetSelect.propTypes = { customMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - defaultMelodies: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + defaultMelodies: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string.isRequired, + tracks: PropTypes.arrayOf(PropTypes.string).isRequired, + })).isRequired, escs: PropTypes.number.isRequired, + onDelete: PropTypes.func.isRequired, onUpdateMelodies: PropTypes.func.isRequired, + selected: PropTypes.string.isRequired, }; function MelodyEditor({ dummy, melodies, onClose, + onDelete, onSave, onWrite, customMelodies, @@ -136,6 +188,7 @@ function MelodyEditor({ const [acceptedMelodies, setAcceptedMelodies] = useState(defaultAccepted); const [isAnyPlaying, setIsAnyPlaying] = useState(false); const latestMelodies = useRef(melodies); + const selectedMelody = useRef(-1); const totalPlaying = useRef(0); const audioContext = useRef(0); @@ -219,7 +272,9 @@ function MelodyEditor({ } function handleMelodiesSave(name) { - onSave(name, latestMelodies.current); + selectedMelody.current = name; + const unique = [...new Set(latestMelodies.current)]; + onSave(name, unique); } function handleMelodiesSelected(selected) { @@ -290,24 +345,28 @@ function MelodyEditor({ {t('common:melodyEditorHeader')} -
- +
+ +
+ +
- - @@ -359,6 +418,7 @@ MelodyEditor.propTypes = { dummy: PropTypes.bool.isRequired, melodies: PropTypes.arrayOf(PropTypes.string).isRequired, onClose: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, onWrite: PropTypes.func.isRequired, writing: PropTypes.bool.isRequired, diff --git a/src/Components/MelodyEditor/style.scss b/src/Components/MelodyEditor/style.scss index dee4b4032..49c6f0c84 100644 --- a/src/Components/MelodyEditor/style.scss +++ b/src/Components/MelodyEditor/style.scss @@ -10,14 +10,44 @@ justify-content: center; align-items: center; + .line-wrapper { + margin-top: 20px; + display: flex; + flex-direction: row; + + .info-wrapper-wrapper { + white-space: nowrap; + } + + select { + margin-left: 10px; + height: 20px; + border: 1px solid silver; + border-radius: 3px; + } + + .melody-selection-wrapper { + display: flex; + flex-direction: row; + .default-btn { + margin-top: -2px; + margin-left: 10px; + button { + width: 100px; + } + } + } + } + .save-melody-wrapper { display: flex; flex-direction: row; input { border: 1px solid silver; - padding: 5px; - width: 210px; + padding-left: 5px; + padding-right: 5px; + line-height: 23px; } button { @@ -34,7 +64,6 @@ position: relative; .sync-wrapper { - margin-top: 20px; .checkbox { display: flex; diff --git a/src/Containers/App/index.jsx b/src/Containers/App/index.jsx index 25a28322d..4b1c96800 100644 --- a/src/Containers/App/index.jsx +++ b/src/Containers/App/index.jsx @@ -921,6 +921,16 @@ class App extends Component { this.setMelodies({ customMelodies: this.loadMelodies() }); } + handleMelodyDelete = (name) => { + const storedMelodies = JSON.parse(localStorage.getItem('melodies')) || []; + const match = storedMelodies.findIndex((melody) => melody.name === name); + if(match >= 0) { + storedMelodies.splice(match, 1); + localStorage.setItem('melodies', JSON.stringify(storedMelodies)); + this.setMelodies({ customMelodies: this.loadMelodies() }); + } + } + handleMelodyWrite = (melodies) => { const { escs } = this.state; const individual = [ ...escs.individual ]; @@ -1017,6 +1027,7 @@ class App extends Component { handleWrite: this.handleMelodyWrite, handleOpen: this.handleMelodyEditorOpen, handleClose: this.handleMelodyEditorClose, + handleDelete: this.handleMelodyDelete, }, ...melodies, }} diff --git a/src/changelog.json b/src/changelog.json index 994a2c35c..148a86784 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -3,9 +3,11 @@ "title": "Unpublished", "items": [ "Show synced screen when all melodies are the same", - "Show info when esc connected count does not match what MSP is reporting", + "Show info when ESC connected count does not match what MSP is reporting", "Melody preset selection", - "Save melodies for later" + "Save melodies for later", + "Load previously saved melodies", + "Updated dependencies" ] }, { diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 204549f84..9067ea283 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -95,7 +95,8 @@ "melodyEditorWrite": "Write Melodies", "melodyEditorSave": "Save", "melodyEditorCustom": "Saved melodies", - "melodyEditorName": "Melody name", + "melodyEditorName": "Save as...", + "melodyDelete": "Delete", "melodyEditorPlay": "Play", "melodyEditorStop": "Stop", "melodyEditorAccept": "Accept", From f03e1bba28de295cd7b9847a019aa9a71b0ce62c Mon Sep 17 00:00:00 2001 From: Chris L Date: Fri, 9 Apr 2021 22:39:17 +0200 Subject: [PATCH 12/21] Fixed typo --- src/translations/en/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 9067ea283..cdafb2a18 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -83,7 +83,7 @@ "openPortSelection": "Open Port Selection", "versionUnsupported": "{{name}} version '{{version}}' (Layout {{layout}}) is not yet supported.

Let us know by opening an
issue.", "motorControl": "Motor Control", - "motorControlText": "

Make your ESCs are properly set up to reflect the state of the sliders.

Eg.: When you have 3D mode enabled, make sure the ESCs are also set up for 3D mode, otherwise the motors might go off with full power.

Also be aware that the motors will not spin if you have bi-directional Dshot enabled on the Flight-controller, but not on the ESC. Which might be the case when flashing from RPM emabled firmware to BlHeli_S.

", + "motorControlText": "

Make sure your ESCs are properly set up to reflect the state of the sliders.

Eg.: When you have 3D mode enabled, make sure the ESCs are also set up for 3D mode, otherwise the motors might go off with full power.

Also be aware that the motors will not spin if you have bi-directional Dshot enabled on the Flight-controller, but not on the ESC. Which might be the case when flashing from RPM emabled firmware to BlHeli_S.

", "enableMotorControl": "Enable motor control", "masterSpeed": "Master Speed", "motorNr": "Motor {{index}}", From d773ee39cc79e8f8dd172ff33741fdf6dc9d6797 Mon Sep 17 00:00:00 2001 From: Chris L Date: Fri, 9 Apr 2021 22:45:45 +0200 Subject: [PATCH 13/21] Improved size of installation banner. --- src/Components/Home/index.jsx | 4 ++++ src/Components/Home/style.scss | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Components/Home/index.jsx b/src/Components/Home/index.jsx index 4e25a7f82..5789f2c4a 100644 --- a/src/Components/Home/index.jsx +++ b/src/Components/Home/index.jsx @@ -13,6 +13,10 @@ function Home({ onOpenMelodyEditor }) { const deferredPrompt = useRef(null); const [showInstall, setShowInstall] = useState(false); + setTimeout(() => { + setShowInstall(true); + }, 1000); + window.addEventListener('beforeinstallprompt', (e) => { deferredPrompt.current = e; setShowInstall(true); diff --git a/src/Components/Home/style.scss b/src/Components/Home/style.scss index 1bd24d772..c06e1a560 100644 --- a/src/Components/Home/style.scss +++ b/src/Components/Home/style.scss @@ -8,7 +8,7 @@ .install-wrapper { position: absolute; - top: 32px; + top: 17px; right: 60px; height: 0; overflow: hidden; @@ -22,7 +22,7 @@ @keyframes slide { from {height: 0;} - to {height: 128px;} + to {height: 150px;} } .install { From 5f8f025ed590eaa0cc253460cfad3280635f2f22 Mon Sep 17 00:00:00 2001 From: Chris L Date: Sat, 10 Apr 2021 01:03:22 +0200 Subject: [PATCH 14/21] Refactored some components --- src/Components/Home/index.jsx | 361 ++++++++++++++-------------- src/Components/PortPicker/index.jsx | 185 ++++++++------ src/Components/Statusbar/index.jsx | 41 ++-- src/changelog.json | 3 +- 4 files changed, 326 insertions(+), 264 deletions(-) diff --git a/src/Components/Home/index.jsx b/src/Components/Home/index.jsx index 5789f2c4a..6ddeea758 100644 --- a/src/Components/Home/index.jsx +++ b/src/Components/Home/index.jsx @@ -8,8 +8,9 @@ import React, { import bluejay from './images/bluejay_logo.png'; import './style.scss'; -function Home({ onOpenMelodyEditor }) { +function Install() { const { t } = useTranslation('common'); + const deferredPrompt = useRef(null); const [showInstall, setShowInstall] = useState(false); @@ -22,210 +23,219 @@ function Home({ onOpenMelodyEditor }) { setShowInstall(true); }); - function Install() { - function handleInstallToHomescreen() { - if(deferredPrompt.current) { - deferredPrompt.current.prompt(); - } + function handleInstallToHomescreen() { + if(deferredPrompt.current) { + deferredPrompt.current.prompt(); } + } - return( -
-
-
- -
- -
+ return( +
+
+
+ +
+
- ); - } - - function HomeColumnLeft() { - return( -
-
-
-

- {t('homeDisclaimerHeader')} -

+
+ ); +} -
-
+function HomeColumnLeft() { + const { t } = useTranslation('common'); -
-

- {t('homeAttributionHeader')} -

+ return( +
+
+
+

+ {t('homeDisclaimerHeader')} +

-
-
+
-
- ); - } - - function HomeColumnCenter() { - return( -
-
+

- {t('homeExperimental')} + {t('homeAttributionHeader')}

-
- {t('homeVersionInfo')} +
+
+
+
+ ); +} - -
+function HomeColumnCenter({ onOpenMelodyEditor }) { + const { t } = useTranslation('common'); -
-

- BLHELI_S -

+ return( +
+
+ +

+ {t('homeExperimental')} +

+ +
+ {t('homeVersionInfo')} + + +
-
-
-
-
-
-
+
+

+ BLHELI_S +

-
-

- Bluejay -

+
+
+
+
+
+
-
- Bluejay +
+

+ Bluejay +

+ +
+ Bluejay + +
+
-
-
- -
- -
+
+
-
-
+
+ +
-
-

- AM32 -

+
+

+ AM32 +

-
-
-
-
+
+
+
- ); - } +
+ ); +} +HomeColumnCenter.propTypes = { onOpenMelodyEditor: PropTypes.func.isRequired }; - function HomeColumnRight() { - return( -
-
-
-

- {t('homeDiscordHeader')} -

- -
- - - Discord - -
+function HomeColumnRight() { + const { t } = useTranslation('common'); -
-

- {t('homeChinaHeader')} -

+ return( +
+
+
+

+ {t('homeDiscordHeader')} +

-
-
+
+ + + Discord + +
-
-

- {t('homeContributionHeader')} -

+
+

+ {t('homeChinaHeader')} +

-
-
+
+
-
-

- {t('whatsNextHeader')} -

+
+

+ {t('homeContributionHeader')} +

-
-
-
-
+
+
+ +
+

+ {t('whatsNextHeader')} +

+ +
+
+
- ); - } +
+ ); +} + +function Home({ onOpenMelodyEditor }) { + const { t } = useTranslation('common'); return (
@@ -254,7 +264,9 @@ function Home({ onOpenMelodyEditor }) {
- +
@@ -263,7 +275,6 @@ function Home({ onOpenMelodyEditor }) {
); } - Home.propTypes = { onOpenMelodyEditor: PropTypes.func.isRequired }; export default Home; diff --git a/src/Components/PortPicker/index.jsx b/src/Components/PortPicker/index.jsx index 0c8a80686..2e5b3455a 100644 --- a/src/Components/PortPicker/index.jsx +++ b/src/Components/PortPicker/index.jsx @@ -4,91 +4,120 @@ import React from 'react'; import './style.scss'; -function PortPicker({ - hasPort, - hasSerial, - open, - onSetPort, - onSetBaudRate, - onConnect, - onDisconnect, - ports, - onChangePort, +function BaudRates({ + handleChange, + disabled, }) { const { t } = useTranslation('common'); const baudRates = [115200, 57600, 38400, 28800, 19200, 14400, 9600, 4800, 2400, 1200]; - function BaudRates() { - const rateElements = baudRates.map((rate) => ( - - )); + const rateElements = baudRates.map((rate) => ( + + )); - return ( - - ); - } + return ( + + ); +} +BaudRates.propTypes = { + disabled: PropTypes.bool.isRequired, + handleChange: PropTypes.func.isRequired, +}; - function Ports() { - const portOptions = ports.map((name, index) => ( - - )); +function PermissionOverlay({ + handleSetPort, + show, +}) { + const { t } = useTranslation('common'); + if(!show) { return( - +
+ +
); } - function PermissionOverlay() { - if(!hasPort) { - return( -
- -
- ); - } + return null; +} +PermissionOverlay.propTypes = { + handleSetPort: PropTypes.func.isRequired, + show: PropTypes.bool.isRequired, +}; - return null; - } +function Ports({ + disabled, + ports, + handleChange, +}) { + const { t } = useTranslation('common'); + + const portOptions = ports.map((name, index) => ( + + )); - function changeBaudRate(e) { + return( + + ); +} +Ports.propTypes = { + disabled: PropTypes.bool.isRequired, + handleChange: PropTypes.func.isRequired, + ports: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, +}; + +function PortPicker({ + hasPort, + hasSerial, + open, + onSetPort, + onSetBaudRate, + onConnect, + onDisconnect, + ports, + onChangePort, +}) { + const { t } = useTranslation('common'); + + function handleBaudRateChange(e) { onSetBaudRate(e.target.value); } - function changePort(e) { + function handlePortChange(e) { onChangePort(e.target.value); } @@ -151,21 +180,31 @@ function PortPicker({ return (
- +
- +
- +
@@ -196,14 +235,12 @@ function PortPicker({
); } - PortPicker.defaultProps = { hasPort: false, hasSerial: false, open: false, ports: [], }; - PortPicker.propTypes = { hasPort: PropTypes.bool, hasSerial: PropTypes.bool, diff --git a/src/Components/Statusbar/index.jsx b/src/Components/Statusbar/index.jsx index f57470f00..3d82ab03a 100644 --- a/src/Components/Statusbar/index.jsx +++ b/src/Components/Statusbar/index.jsx @@ -8,11 +8,34 @@ import React, { import './style.scss'; +function BatteryState({ + danger, + text, +}) { + const { t } = useTranslation('common'); + + if(text) { + return ( + + {`${t('battery')} ${text}`} + + ); + } + + return null; +} +BatteryState.defaultProps = { text: undefined }; +BatteryState.propTypes = { + danger: PropTypes.bool.isRequired, + text: PropTypes.string, +}; + const Statusbar = forwardRef(({ packetErrors, version, }, ref) => { const { t } = useTranslation('common'); + const cellLimit = 3.7; const [utilization, setUtilization] = useState({ up: 0, @@ -43,18 +66,6 @@ const Statusbar = forwardRef(({ }, })); - function BatteryState() { - if(batteryState.text) { - return ( - - {`${t('battery')} ${batteryState.text}`} - - ); - } - - return null; - } - return (
@@ -65,7 +76,10 @@ const Statusbar = forwardRef(({ {`${t('statusbarPacketError')} ${packetErrors}`} - + {version} @@ -73,7 +87,6 @@ const Statusbar = forwardRef(({
); }); - Statusbar.propTypes = { packetErrors: PropTypes.number.isRequired, version: PropTypes.string.isRequired, diff --git a/src/changelog.json b/src/changelog.json index 148a86784..d214d02a1 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -7,7 +7,8 @@ "Melody preset selection", "Save melodies for later", "Load previously saved melodies", - "Updated dependencies" + "Updated dependencies", + "Refactoring" ] }, { From b779c7b68bf9e02ee98af53beca9d768b0a5dc29 Mon Sep 17 00:00:00 2001 From: Hugo Chiang <31283897+DusKing1@users.noreply.github.com> Date: Sat, 10 Apr 2021 17:17:05 +0800 Subject: [PATCH 15/21] Tweak the motorControlText. (#95) Text adaptation. --- src/translations/en/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en/common.json b/src/translations/en/common.json index cdafb2a18..a68f96813 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -83,7 +83,7 @@ "openPortSelection": "Open Port Selection", "versionUnsupported": "{{name}} version '{{version}}' (Layout {{layout}}) is not yet supported.

Let us know by opening an issue.", "motorControl": "Motor Control", - "motorControlText": "

Make sure your ESCs are properly set up to reflect the state of the sliders.

Eg.: When you have 3D mode enabled, make sure the ESCs are also set up for 3D mode, otherwise the motors might go off with full power.

Also be aware that the motors will not spin if you have bi-directional Dshot enabled on the Flight-controller, but not on the ESC. Which might be the case when flashing from RPM emabled firmware to BlHeli_S.

", + "motorControlText": "

Make sure your ESCs are properly set up to reflect the state of the sliders.

Eg.: When you enabled 3D mode in your flight controller, make sure the ESCs are also set up for 3D mode, otherwise the motors might go off with full power.

Also be aware that the motors will not spin if you have bi-directional Dshot enabled on the Flight-controller, but the ESC does not support it. Which might be the case when flashing from RPM emabled firmware to BlHeli_S.

", "enableMotorControl": "Enable motor control", "masterSpeed": "Master Speed", "motorNr": "Motor {{index}}", From 9e8ba39c4c661b53584359d66303bc49876f0a51 Mon Sep 17 00:00:00 2001 From: Chris L Date: Sat, 10 Apr 2021 12:50:20 +0200 Subject: [PATCH 16/21] Mobile improvements. #97, #96 --- src/Components/App/style.scss | 7 ++- src/Components/Buttonbar/index.jsx | 26 +++++++---- src/Components/Buttonbar/style.scss | 16 ++++--- src/Components/MainContent/style.scss | 4 ++ src/Components/MelodyEditor/index.jsx | 2 +- src/Components/MelodyEditor/style.scss | 65 ++++++++++++++++++++++++-- src/Components/PortPicker/style.scss | 4 ++ src/Components/Statusbar/style.scss | 10 ++++ src/settings.json | 2 +- 9 files changed, 112 insertions(+), 24 deletions(-) diff --git a/src/Components/App/style.scss b/src/Components/App/style.scss index f3138224f..f727273cf 100644 --- a/src/Components/App/style.scss +++ b/src/Components/App/style.scss @@ -125,6 +125,10 @@ body { background-size: contain; margin-left: 23px; margin-top: 10px; + + @media only screen and (max-width: 600px) { + margin-left: 15px; + } } .hidden { @@ -238,12 +242,13 @@ body { margin-bottom: 50px; @media only screen and (max-width: 600px) { - margin-bottom: 90px; + margin-bottom: 120px; } @media screen and (max-width: 768px) { padding-left: 10px; padding-right: 10px; + padding-top: 13px; } } diff --git a/src/Components/Buttonbar/index.jsx b/src/Components/Buttonbar/index.jsx index 2a0c503cb..cc6a777cf 100644 --- a/src/Components/Buttonbar/index.jsx +++ b/src/Components/Buttonbar/index.jsx @@ -34,15 +34,13 @@ function Buttonbar({ function MelodyEditorButton() { if(showMelodyEditor) { return ( -
- -
+ ); } @@ -51,6 +49,12 @@ function Buttonbar({ return (
+
+
+ +
+
+
- +
+ +
); diff --git a/src/Components/Buttonbar/style.scss b/src/Components/Buttonbar/style.scss index cde6d4383..affca8c83 100644 --- a/src/Components/Buttonbar/style.scss +++ b/src/Components/Buttonbar/style.scss @@ -14,6 +14,11 @@ flex-direction: row; justify-content: space-between; + @media only screen and (max-width: 600px) { + padding-bottom: 0px; + padding-top: 8px; + } + & > div { display: flex; @@ -37,25 +42,22 @@ & > div { justify-content: flex-start; + &.buttons-bottom, &.buttons-left, &.buttons-right { - margin-bottom: 10px; + margin-bottom: 8px; margin-left: 5px; margin-right: 5px; .btn { flex: 1; - margin-left: 5px; - margin-right: 5px; + margin-left: 2px; + margin-right: 2px; button { width: 100%; } } } - - &.buttons-left { - margin-bottom: 0px; - } } } diff --git a/src/Components/MainContent/style.scss b/src/Components/MainContent/style.scss index 7bbad0644..3547b32ae 100644 --- a/src/Components/MainContent/style.scss +++ b/src/Components/MainContent/style.scss @@ -20,6 +20,10 @@ font-size: 11px; font-family: 'open_sansregular', Arial; padding: 5px 7px 5px 7px; + + @media screen and (max-width: 768px) { + margin-bottom: 18px; + } } @media only screen and (max-width: 600px) { diff --git a/src/Components/MelodyEditor/index.jsx b/src/Components/MelodyEditor/index.jsx index 6843e512d..22534a245 100644 --- a/src/Components/MelodyEditor/index.jsx +++ b/src/Components/MelodyEditor/index.jsx @@ -371,7 +371,7 @@ function MelodyEditor({ onSave={handleMelodiesSave} /> -
+
{!sync && melodyElements} {sync && diff --git a/src/Components/MelodyEditor/style.scss b/src/Components/MelodyEditor/style.scss index 49c6f0c84..ff5d980d4 100644 --- a/src/Components/MelodyEditor/style.scss +++ b/src/Components/MelodyEditor/style.scss @@ -29,14 +29,35 @@ .melody-selection-wrapper { display: flex; flex-direction: row; + margin-bottom: 6px; + + .info-wrapper-wrapper { + display: none; + } + .default-btn { margin-top: -2px; margin-left: 10px; + button { width: 100px; } } } + + @media screen and (max-width: 768px) { + flex-direction: column; + + select { + margin-left: 0px; + } + + .melody-selection-wrapper { + .default-btn button { + width: inherit; + } + } + } } .save-melody-wrapper { @@ -50,9 +71,16 @@ line-height: 23px; } - button { + .default-btn { margin-left: 10px; - width: 100px; + + button { + width: 100px; + + @media screen and (max-width: 768px) { + width: inherit; + } + } } } @@ -63,6 +91,10 @@ width: 80%; position: relative; + @media screen and (max-width: 600px) { + width: 90%; + } + .sync-wrapper { .checkbox { @@ -76,8 +108,6 @@ margin-left: -10px; margin-right: -10px; - - &.all { .esc-melody-wrapper { width: 100%; @@ -85,6 +115,33 @@ } } + @media screen and (max-width: 600px) { + .melody-editor-escs.single { + .esc-melody { + margin-top: 5px; + margin-bottom: 5px; + + header { + flex-direction: column; + border: 0px; + padding-bottom: 0px; + margin-bottom: 4px; + + .default-btn { + margin-top: 2px; + margin-left: -2px; + margin-right: -2px; + + button { + margin-left: 2px; + margin-right: 2px; + } + } + } + } + } + } + .button-wrapper { display: flex; justify-content: flex-end; diff --git a/src/Components/PortPicker/style.scss b/src/Components/PortPicker/style.scss index a1b824b14..26e3c64b7 100644 --- a/src/Components/PortPicker/style.scss +++ b/src/Components/PortPicker/style.scss @@ -90,6 +90,10 @@ display: flex; flex-direction: column; + @media only screen and (max-width: 600px) { + margin-right: 6px; + } + button { background: none; border: 0px; diff --git a/src/Components/Statusbar/style.scss b/src/Components/Statusbar/style.scss index 39eda62a3..99a110e8e 100644 --- a/src/Components/Statusbar/style.scss +++ b/src/Components/Statusbar/style.scss @@ -27,4 +27,14 @@ padding: 0; border: 0; } + + @media only screen and (max-width: 600px) { + & { + display: flex; + justify-content: center; + span { + float: none; + } + } + } } diff --git a/src/settings.json b/src/settings.json index 41b3065f7..88d0bc998 100644 --- a/src/settings.json +++ b/src/settings.json @@ -1,4 +1,4 @@ { - "version": "0.11.0", + "version": "v0.11.0", "corsProxy": "https://bubblesort.me/?" } From 0a87402a8f0d20c7e57b288bbd140d68d4fbf2b0 Mon Sep 17 00:00:00 2001 From: Chris L Date: Sat, 10 Apr 2021 14:01:45 +0200 Subject: [PATCH 17/21] Moved battery display to Motor box #97, #96 --- src/Components/App/index.jsx | 13 +------ src/Components/MainContent/index.jsx | 4 ++ src/Components/MotorControl/index.jsx | 52 ++++++++++++++++++++++++-- src/Components/MotorControl/style.scss | 26 +++++++++++-- src/Components/Statusbar/index.jsx | 46 ----------------------- src/changelog.json | 1 + 6 files changed, 77 insertions(+), 65 deletions(-) diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx index f173bd7a6..4fe5bc6a4 100644 --- a/src/Components/App/index.jsx +++ b/src/Components/App/index.jsx @@ -35,18 +35,6 @@ function App({ const { t } = useTranslation('common'); const statusbarRef = useRef(); - /* istanbul ignore next */ - useInterval(async() => { - if(serial.open && !actions.isReading && !serial.fourWay) { - if(serial.port.getBatteryState) { - const batteryState = await serial.port.getBatteryState(); - statusbarRef.current.updateBatteryState(batteryState); - } - } else { - statusbarRef.current.updateBatteryState(null); - } - }, 1000); - /* istanbul ignore next */ useInterval(async() => { if(serial.port && serial.port.getUtilization) { @@ -156,6 +144,7 @@ function App({ onSingleMotorSpeed={onSingleMotorSpeed} onWriteSetup={escs.actions.handleWriteSetup} open={serial.open} + port={serial.port} progress={escs.progress} settings={escs.master} /> diff --git a/src/Components/MainContent/index.jsx b/src/Components/MainContent/index.jsx index 34a6ea99f..db21fa4ac 100644 --- a/src/Components/MainContent/index.jsx +++ b/src/Components/MainContent/index.jsx @@ -38,6 +38,7 @@ function MainContent({ onSingleMotorSpeed, connected, fourWay, + port, }) { const { t } = useTranslation('common'); @@ -88,6 +89,7 @@ function MainContent({ if(!fourWay && !actions.isReading) { return ( { + if(getBatteryState) { + const state = await getBatteryState(); + + if(state && state.cellCount > 0) { + const danger = (state.voltage / state.cellCount) < cellLimit; + setBatteryState({ + text: `${state.cellCount}S @ ${state.voltage}V`, + danger, + }); + } else { + setBatteryState({ + text: null, + danger: false, + }); + } + } + }, 1000); + + if(batteryState.text) { + return ( + + {`${t('battery')} ${batteryState.text}`} + + ); + } + + return null; +} +BatteryState.propTypes = { getBatteryState: PropTypes.func.isRequired }; + function MotorControl({ + getBatteryState, motorCount, onAllUpdate, onSingleUpdate, @@ -148,13 +191,17 @@ function MotorControl({ dangerouslySetInnerHTML={{ __html: t('motorControlText') }} /> -
+
+ +
@@ -175,13 +222,12 @@ function MotorControl({
); } - MotorControl.defaultProps = { motorCount: 0, startValue: 1000, }; - MotorControl.propTypes = { + getBatteryState: PropTypes.func.isRequired, motorCount: PropTypes.number, onAllUpdate: PropTypes.func.isRequired, onSingleUpdate: PropTypes.func.isRequired, diff --git a/src/Components/MotorControl/style.scss b/src/Components/MotorControl/style.scss index ef293ffe5..9d09fefd9 100644 --- a/src/Components/MotorControl/style.scss +++ b/src/Components/MotorControl/style.scss @@ -3,10 +3,28 @@ width: 100%; // margin-bottom: 65px; - .spacer-box { - p { - font-weight: normal; - padding-bottom: 10px; + .gui-box { + .spacer-box { + p { + font-weight: normal; + padding-bottom: 10px; + } + } + + .line-wrapper { + display: flex; + align-items: center; + margin-bottom: 3px; + + &>* { + flex: 1; + padding: 0px; + margin-bottom: 7px; + } + + .battery-state { + font-weight: bold; + } } } diff --git a/src/Components/Statusbar/index.jsx b/src/Components/Statusbar/index.jsx index 3d82ab03a..00dd748aa 100644 --- a/src/Components/Statusbar/index.jsx +++ b/src/Components/Statusbar/index.jsx @@ -8,59 +8,18 @@ import React, { import './style.scss'; -function BatteryState({ - danger, - text, -}) { - const { t } = useTranslation('common'); - - if(text) { - return ( - - {`${t('battery')} ${text}`} - - ); - } - - return null; -} -BatteryState.defaultProps = { text: undefined }; -BatteryState.propTypes = { - danger: PropTypes.bool.isRequired, - text: PropTypes.string, -}; - const Statusbar = forwardRef(({ packetErrors, version, }, ref) => { const { t } = useTranslation('common'); - const cellLimit = 3.7; const [utilization, setUtilization] = useState({ up: 0, down: 0, }); - const [batteryState, setBatteryState] = useState({ - text: null, - danger: false, - }); useImperativeHandle(ref, () => ({ - updateBatteryState(state) { - if(state && state.cellCount > 0) { - const danger = (state.voltage / state.cellCount) < cellLimit; - setBatteryState({ - text: `${state.cellCount}S @ ${state.voltage}V`, - danger, - }); - } else { - setBatteryState({ - text: null, - danger: false, - }); - } - }, updateUtilization(utilization) { setUtilization(utilization); }, @@ -76,11 +35,6 @@ const Statusbar = forwardRef(({ {`${t('statusbarPacketError')} ${packetErrors}`} - - {version} diff --git a/src/changelog.json b/src/changelog.json index d214d02a1..8ddc58365 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -7,6 +7,7 @@ "Melody preset selection", "Save melodies for later", "Load previously saved melodies", + "Styling improvements for mobile", "Updated dependencies", "Refactoring" ] From dfe1c2ff4bc543475c8415addfb008741152063e Mon Sep 17 00:00:00 2001 From: Chris L Date: Sat, 10 Apr 2021 14:05:33 +0200 Subject: [PATCH 18/21] Adjusted test cases. --- .../Buttonbar/__tests__/index.test.jsx | 2 +- .../Statusbar/__tests__/index.test.jsx | 21 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/Components/Buttonbar/__tests__/index.test.jsx b/src/Components/Buttonbar/__tests__/index.test.jsx index 2ce7ddcb6..c414bb8c5 100644 --- a/src/Components/Buttonbar/__tests__/index.test.jsx +++ b/src/Components/Buttonbar/__tests__/index.test.jsx @@ -38,7 +38,7 @@ test('loads and displays Buttonbar', () => { expect(screen.getByText(/escButtonWrite/i)).toBeInTheDocument(); expect(screen.getByText(/escButtonFlashAll/i)).toBeInTheDocument(); expect(screen.getByText(/escButtonSaveLog/i)).toBeInTheDocument(); - expect(screen.getByText(/escButtonOpenMelodyEditor/i)).toBeInTheDocument(); + expect(screen.getAllByText(/escButtonOpenMelodyEditor/i).length).toEqual(2); }); test('trigger onSaveLog', () => { diff --git a/src/Components/Statusbar/__tests__/index.test.jsx b/src/Components/Statusbar/__tests__/index.test.jsx index b87d244cb..f463769f6 100644 --- a/src/Components/Statusbar/__tests__/index.test.jsx +++ b/src/Components/Statusbar/__tests__/index.test.jsx @@ -24,27 +24,6 @@ test('loads and displays StatusBar', () => { expect(screen.getByText(/statusbarPacketError 0/i)).toBeInTheDocument(); expect(screen.getByText('version')).toBeInTheDocument(); - act(()=> { - ref.current.updateBatteryState({ - cellCount: 1, - voltage: 4.2, - }); - }); - expect(screen.getByText('battery 1S @ 4.2V')).toBeInTheDocument(); - - act(()=> { - ref.current.updateBatteryState({ - cellCount: 1, - voltage: 3.5, - }); - }); - expect(screen.getByText('battery 1S @ 3.5V')).toBeInTheDocument(); - - act(()=> { - ref.current.updateBatteryState(null); - }); - expect(screen.queryByText(/battery/i)).not.toBeInTheDocument(); - act(()=> { ref.current.updateUtilization({ up: 10, From 8d668027671740bafe42d7fb509e3cf9b548668d Mon Sep 17 00:00:00 2001 From: Chris L Date: Sat, 10 Apr 2021 14:06:49 +0200 Subject: [PATCH 19/21] Fixed s tyling --- src/Components/MotorControl/style.scss | 5 +++++ src/Components/Statusbar/style.scss | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Components/MotorControl/style.scss b/src/Components/MotorControl/style.scss index 9d09fefd9..00140ca35 100644 --- a/src/Components/MotorControl/style.scss +++ b/src/Components/MotorControl/style.scss @@ -24,6 +24,11 @@ .battery-state { font-weight: bold; + + &.danger { + font-weight: bold; + color: red; + } } } } diff --git a/src/Components/Statusbar/style.scss b/src/Components/Statusbar/style.scss index 99a110e8e..b974f7a62 100644 --- a/src/Components/Statusbar/style.scss +++ b/src/Components/Statusbar/style.scss @@ -14,11 +14,6 @@ padding-right: 10px; margin-right: 10px; border-right: 1px solid #7d7d79; - - &.danger { - font-weight: bold; - color: red; - } } .version { From 77dd63bf4d5846458c9fe16127bd603ddf8a53a1 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 10 Apr 2021 14:15:34 +0200 Subject: [PATCH 20/21] New Crowdin updates (#92) Translation updates --- src/translations/de/common.json | 14 +++++++++++++- src/translations/zh-CN/common.json | 16 ++++++++++++++-- src/translations/zh-TW/common.json | 14 +++++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/translations/de/common.json b/src/translations/de/common.json index 8345fecb7..2d2493bcf 100644 --- a/src/translations/de/common.json +++ b/src/translations/de/common.json @@ -83,6 +83,7 @@ "openPortSelection": "Portauswahl öffnen", "versionUnsupported": "{{name}} Version '{{version}}' (Layout {{layout}}) wird noch nicht unterstützt.

Lass es uns wissen indem du einen Fehlerbericht erstellst.", "motorControl": "Motorsteuerung", + "motorControlText": "

Stelle sicher das deine ESCs entsprechend der Slider Positionen konfiguriert sind.

zB.: Wenn du den 3D Modus aktiviert hast, stelle sicher, dass deinen ESCs entsprechend konfiguriert sind, ansonsten können die Motoren mit voller Geschwindigkeit anlaufen.

Die Motoren werden nicht anlaufen wenn am Flugregler bi-directtinal Dshot aktiviert ist, aber nicht auf den ESCs, was beispielsweise passieren könnte wenn du von einer RPM firmware auf klassisches BlHeli_S zurück flasht.

", "enableMotorControl": "Motorsteuerung aktivieren", "masterSpeed": "Geschwindigkeit aller Motoren", "motorNr": "Motor {{index}}", @@ -91,12 +92,17 @@ "homeChinaHeader": "Für unsere chinesischen Besucher", "homeChinaText": "Sag deinen Freunden hinter der großen chinesischen Firewall, dass sie uns über einen lokalen Spiegel direkt in China erreichen können.", "melodyEditorHeader": "Melodie Editor", + "melodyEditorWrite": "Melodien schreiben", "melodyEditorSave": "Speichern", + "melodyEditorCustom": "Gespeicherte Melodien", + "melodyEditorName": "Speichern als...", + "melodyDelete": "Löschen", "melodyEditorPlay": "Abspielen", "melodyEditorStop": "Stop", "melodyEditorAccept": "Übernehmen", "melodyEditorPlayAll": "Alle abspielen", "melodyEditorStopAll": "Alle stoppen", + "melodyPresetsLabel": "Melodie wählen", "homeAttributionHeader": "Beteiligung", "homeAttributionText": "Dieses Projekt wurde stark vom BLHeli Configurator inspiriert. Der Großteil der Benutzeroberfläche wurde von Grund auf neu geschrieben, aber viele der mit dem Flashen und Firmware-Handling zusammenhängenden Low-Level-Funktionen wurden wiederverwendet - also großen Dank an alle, die am ursprünglichen BLHeli Configurator beteiligt waren.", "syncMelodies": "Melodien synchronisieren", @@ -114,5 +120,11 @@ "settings": "Einstellungen", "openMelodyEditor": "Melodie Editor öffnen", "addToHomeScreen": "Zum Startbildschirm hinzufügen", - "homeInstall": "

Der ESC-Konfigurator funktioniert auch offline, sobald dieser zum Homescreen hinzugefügt wurde. Firmware-Dateien welche einmal geflashed wurden, sind danach auch offline verfügbar.

Einstellungen können offline jederzeit angepasst werden.

" + "homeInstall": "

Der ESC-Konfigurator funktioniert auch offline, sobald dieser zum Homescreen hinzugefügt wurde. Firmware-Dateien welche einmal geflashed wurden, sind danach auch offline verfügbar.

Einstellungen können offline jederzeit angepasst werden.

", + "escMissingHeader": "Fehlt ein ESC?", + "escMissingText": "Die Anzahl der verfügbaren ESCs stimmt nicht mit der Anzahl überein die der Flugcontroller meldet . Dies kann mehrere Gründe haben:", + "escMissingHint": "Manchmal kann es notwendig sein den 'Einstellungen lesen' Knopf erneut zu drücken um das korrekte Ergebnis zu erhalten.", + "escMissing1": "Der ESC war noch nicht bereit. Dies kann zum Beispiel passieren, wenn er eine Melodie abspielt. Auch ein vorheriges Flashen kann schief gelaufen sein, dadurch braucht der ESC länger zum Starten als üblich.", + "escMissing2": "Es sind tatsächlich keine weiteren ESCs verbunden.", + "escMissing3": "Die MCU des ESC ist defekt." } diff --git a/src/translations/zh-CN/common.json b/src/translations/zh-CN/common.json index 2382cbec2..c6d7ceb76 100644 --- a/src/translations/zh-CN/common.json +++ b/src/translations/zh-CN/common.json @@ -83,6 +83,7 @@ "openPortSelection": "打开端口选择", "versionUnsupported": "{{name}} 版本 '{{version}}' (布局 {{layout}}) 尚未支持。

请打开一个问题来让我们知道。", "motorControl": "电机控制", + "motorControlText": "

请确保您的ESC正确设置以反映滑块的状态。

例如,当您在飞控中启用3D模式后, 请确保 ESC 也已经设置为 3D 模式,否则电机可能在解锁瞬间满负荷运行。

同样地,如果你在飞控上使用双向 Dshot,而 ESC 固件并不支持双向 Dshot,那么电机将不会旋转。从已有 RPM 回传的固件向前回滚刷入 BlHeli_S 时可能会出现这种情况。

", "enableMotorControl": "启用电机控制", "masterSpeed": "主速度", "motorNr": "电机 {{index}}", @@ -91,12 +92,17 @@ "homeChinaHeader": "对于我们的中国用户", "homeChinaText": "告诉你的朋友们,在GFW下,他们可以在中国通过直接访问 内地的镜像站 来使用我们的应用程序。", "melodyEditorHeader": "旋律编辑器", + "melodyEditorWrite": "写入旋律", "melodyEditorSave": "保存", + "melodyEditorCustom": "保存旋律", + "melodyEditorName": "另存为...", + "melodyDelete": "删除", "melodyEditorPlay": "播放", "melodyEditorStop": "停止", "melodyEditorAccept": "接受", "melodyEditorPlayAll": "播放全部", "melodyEditorStopAll": "停止全部", + "melodyPresetsLabel": "选择一个旋律", "homeAttributionHeader": "致谢", "homeAttributionText": "此项目受 BLHeli Configurator 的强烈启发。 大多数用户界面都是从零开始重写的,但大量与刷新和固件处理有关的低级物品已被重新使用 - 所以对所有参与原来的 BLHeli Configurator 的人致以最高敬意。", "syncMelodies": "同步的旋律", @@ -113,6 +119,12 @@ "battery": "电池:", "settings": "设置", "openMelodyEditor": "打开旋律编辑器", - "addToHomeScreen": "Add to Homescreen", - "homeInstall": "

ESC-Configurator can also be used offline when added to the homescreen. Once flashed, firmware files are available offline.

Settings can always be adjusted when offline.

" + "addToHomeScreen": "添加到主屏幕", + "homeInstall": "

ESC 配置程序 当被添加到主屏幕之后即可被离线使用。 烧录一次固件后,固件文件即可被保存到本地。

您始终可以在离线状态下配置您的设置。

", + "escMissingHeader": "缺少某个 ESC ?", + "escMissingText": "可用的 ESC 数量与飞行控制器报告的数量不匹配。这可能有多种原因:", + "escMissingHint": "如果读取结果并不如您所愿,请尝试再次点击 “读取设置” 按钮。", + "escMissing1": "ESC 尚未准备就绪。例如,如果 ESC 正在播放旋律,则可能发生这种情况。 同样,先前刷入的固件可能是错误的固件,并且它需要更长的时间才能完成启动。", + "escMissing2": "只是 没有连接更多的 ESC 。", + "escMissing3": "ESC 的 MCU 存在缺陷 。" } diff --git a/src/translations/zh-TW/common.json b/src/translations/zh-TW/common.json index 1b681976e..06eedce07 100644 --- a/src/translations/zh-TW/common.json +++ b/src/translations/zh-TW/common.json @@ -83,6 +83,7 @@ "openPortSelection": "Open Port Selection", "versionUnsupported": "{{name}} version '{{version}}' (Layout {{layout}}) is not yet supported.

Let us know by opening an issue.", "motorControl": "Motor Control", + "motorControlText": "

Make sure your ESCs are properly set up to reflect the state of the sliders.

Eg.: When you enabled 3D mode in your flight controller, make sure the ESCs are also set up for 3D mode, otherwise the motors might go off with full power.

Also be aware that the motors will not spin if you have bi-directional Dshot enabled on the Flight-controller, but the ESC does not support it. Which might be the case when flashing from RPM emabled firmware to BlHeli_S.

", "enableMotorControl": "Enable motor control", "masterSpeed": "Master Speed", "motorNr": "Motor {{index}}", @@ -91,12 +92,17 @@ "homeChinaHeader": "For our Chinese visitors", "homeChinaText": "Tell your friends behind the great firewall of China, that they can reach us via a local mirror directly in China.", "melodyEditorHeader": "Melody Editor", + "melodyEditorWrite": "Write Melodies", "melodyEditorSave": "Save", + "melodyEditorCustom": "Saved melodies", + "melodyEditorName": "Save as...", + "melodyDelete": "Delete", "melodyEditorPlay": "Play", "melodyEditorStop": "Stop", "melodyEditorAccept": "Accept", "melodyEditorPlayAll": "Play all", "melodyEditorStopAll": "Stop all", + "melodyPresetsLabel": "Select a melody", "homeAttributionHeader": "Attribution", "homeAttributionText": "This project was heavily inspired by the Blheli Configurator. Most of the UI has been re-written from scratch but a lot of the low level stuff related to flashing and firmware handling have been re-used - so a big shout out to everyone involved in the original Blheli Configurator.", "syncMelodies": "Synchronize Melodies", @@ -114,5 +120,11 @@ "settings": "Settings", "openMelodyEditor": "Open Melody Editor", "addToHomeScreen": "Add to Homescreen", - "homeInstall": "

ESC-Configurator can also be used offline when added to the homescreen. Once flashed, firmware files are available offline.

Settings can always be adjusted when offline.

" + "homeInstall": "

ESC-Configurator can also be used offline when added to the homescreen. Once flashed, firmware files are available offline.

Settings can always be adjusted when offline.

", + "escMissingHeader": "Missing an ESC?", + "escMissingText": "The amount of available ESCs does not match what the flight controller is reporting. This can have multiple reasons:", + "escMissingHint": "If the result is not what you are expecting, try clicking the 'Read Setup' button again.", + "escMissing1": "The ESC was not ready yet. This can for example happen if it is playing a melody. Also a previous flash could have gone wrong and it needs longer to boot up than usual.", + "escMissing2": "There are simply not more ESCs connected.", + "escMissing3": "The MCU of the ESC is defective." } From 13dba5009ae09110c8e2d6cdef1fe428c444358a Mon Sep 17 00:00:00 2001 From: Chris L Date: Sat, 10 Apr 2021 14:44:34 +0200 Subject: [PATCH 21/21] Bumped version numbers --- package.json | 2 +- src/changelog.json | 2 +- src/settings.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 4743ff1bf..aeff47e61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esc-configurator", - "version": "0.11.0", + "version": "0.12.0", "private": true, "license": "AGPL-3.0", "dependencies": { diff --git a/src/changelog.json b/src/changelog.json index 8ddc58365..46cdc1d19 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -1,6 +1,6 @@ [ { - "title": "Unpublished", + "title": "0.12.0", "items": [ "Show synced screen when all melodies are the same", "Show info when ESC connected count does not match what MSP is reporting", diff --git a/src/settings.json b/src/settings.json index 88d0bc998..7b4f95fad 100644 --- a/src/settings.json +++ b/src/settings.json @@ -1,4 +1,4 @@ { - "version": "v0.11.0", + "version": "v0.12.0", "corsProxy": "https://bubblesort.me/?" }