diff --git a/package-lock.json b/package-lock.json index fcc75ae..267d3a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@hello-pangea/color-picker": "^3.2.2", "axios": "^1.3.5", "bootstrap": "^5.2.3", + "bootstrap-icons": "^1.10.5", "crypto-js": "^4.1.1", "formik": "^2.2.9", "javascript-color-gradient": "^2.4.4", @@ -5211,6 +5212,21 @@ "@popperjs/core": "^2.11.6" } }, + "node_modules/bootstrap-icons": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.10.5.tgz", + "integrity": "sha512-oSX26F37V7QV7NCE53PPEL45d7EGXmBgHG3pDpZvcRaKVzWMqIRL9wcqJUyEha1esFtM3NJzvmxFXDxjJYD0jQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ] + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index 1f76570..b32ef3b 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@hello-pangea/color-picker": "^3.2.2", "axios": "^1.3.5", "bootstrap": "^5.2.3", + "bootstrap-icons": "^1.10.5", "crypto-js": "^4.1.1", "formik": "^2.2.9", "javascript-color-gradient": "^2.4.4", diff --git a/server/app.js b/server/app.js index 03cbe17..e24b6f3 100644 --- a/server/app.js +++ b/server/app.js @@ -19,6 +19,10 @@ app.use((req, res, next) => { next(); }); +app.get("/api/getUsedPins", (req, res) => { + return res.send({ usedPins: Object.values(picoController) }); +}) + app.get("/api/resetSettings", (req, res) => { return res.send({ success: true }); }); @@ -119,6 +123,12 @@ app.get("/api/getLedOptions", (req, res) => { A2: null, }, usedPins: Object.values(picoController), + pledType: 1, + pledPin1: 12, + pledPin2: 13, + pledPin3: 14, + pledPin4: 15, + pledColor: 65280, }); }); @@ -270,6 +280,7 @@ app.get("/api/getMemoryReport", (req, res) => { }); app.post("/api/*", (req, res) => { + console.log(req.body); return res.send(req.body); }); diff --git a/src/App.js b/src/App.js index 8b8ae45..f105c44 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,8 @@ import React, { useContext, useState } from 'react'; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; + import { AppContextProvider } from './Contexts/AppContext'; + import Navigation from './Components/Navigation' import HomePage from './Pages/HomePage' diff --git a/src/Components/ColorPicker.js b/src/Components/ColorPicker.js new file mode 100644 index 0000000..01e3064 --- /dev/null +++ b/src/Components/ColorPicker.js @@ -0,0 +1,112 @@ +import React, { useContext, useEffect, useState } from 'react'; +import Button from 'react-bootstrap/Button'; +import Col from 'react-bootstrap/Col'; +import Container from 'react-bootstrap/Container'; +import Form from 'react-bootstrap/Form'; +import Overlay from 'react-bootstrap/Overlay'; +import Popover from 'react-bootstrap/Popover'; +import Row from 'react-bootstrap/Row'; +import { SketchPicker } from '@hello-pangea/color-picker'; + +import { AppContext } from '../Contexts/AppContext'; +import LEDColors from '../Data/LEDColors'; + +import './ColorPicker.scss'; + +const ledColors = LEDColors.map(c => ({ title: c.name, color: c.value})); +const customColors = (colors) => colors.map(c => ({ title: c, color: c })); + +const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show, target, title, ...props }) => { + const { savedColors, setSavedColors } = useContext(AppContext); + const [colorPalette, setColorPalette] = useState([...ledColors, ...customColors(savedColors)]); + const [colorTypes, setColorTypes] = useState(types); + const [selectedColor, setSelectedColor] = useState('#000000'); + const [selectedColorType, setSelectedColorType] = useState(types[0]); + + const deleteCurrentColor = () => { + const colorIndex = savedColors.indexOf(selectedColor); + if (colorIndex < 0) + return; + + const newColors = [...savedColors]; + newColors.splice(colorIndex, 1); + setSavedColors(newColors); + setColorPalette([...ledColors, ...customColors(newColors)]); + }; + + const saveCurrentColor = () => { + if (!selectedColor || colorPalette.filter(c => c.color === selectedColor).length > 0) + return; + + const newColors = [...savedColors]; + newColors.push(selectedColor); + setSavedColors(newColors); + setColorPalette([...ledColors, ...customColors(newColors)]); + }; + + const selectColor = (c, e) => { + if (onChange) + onChange(c.hex, e); + + selectedColorType.value = c.hex; + + setSelectedColor(c.hex); + setColorTypes(colorTypes); + }; + + useEffect(() => { + // Hide color picker when anywhere but picker is clicked + window.addEventListener('click', (e) => onDismiss(e)); + setSelectedColorType(colorTypes[0]); + setSelectedColor(colorTypes[0].value); + }, []); + + return ( + + e.stopPropagation()}> + +
{title}
+ + {colorTypes.map(((o, i) => + setSelectedColorType(o)} + > + {o.title && {o.title}} +
+
+
+ ))} +
+ + + selectColor(c, e)} + disableAlpha={true} + presetColors={colorPalette} + width={180} + /> + + +
+ + +
+
+
+
+ ) +}; + +export default ColorPicker; diff --git a/src/Components/ColorPicker.scss b/src/Components/ColorPicker.scss new file mode 100644 index 0000000..91de0c9 --- /dev/null +++ b/src/Components/ColorPicker.scss @@ -0,0 +1,39 @@ +.color-picker { + user-select: none; + padding-bottom: 10px; + + h6 { + margin-top: 8px; + } + + .color-option { + justify-content: flex-start; + cursor: pointer; + font-size: 0.8rem; + + label { + cursor: pointer; + margin: 0 auto 4px; + text-align: center; + width: 100%; + font-size: 0.85rem; + } + + &.selected { + background-color: #DEDEDE; + } + } + + .color { + display: flex; + + height: 40px !important; + width: 40px !important; + margin: 0 auto 10px; + border: 1px solid black; + } + + .sketch-picker { + margin-top: 10px; + } +} \ No newline at end of file diff --git a/src/Components/FormControl.js b/src/Components/FormControl.js index ecaf226..fde5404 100644 --- a/src/Components/FormControl.js +++ b/src/Components/FormControl.js @@ -1,10 +1,10 @@ import React from 'react'; import { Form } from 'react-bootstrap'; -const FormControl = ({ label, error, groupClassName, ...props }) => { +const FormControl = ({ onClick, label, error, groupClassName, hidden, labelClassName, ...props }) => { return ( - - {label} + diff --git a/src/Contexts/AppContext.js b/src/Contexts/AppContext.js index 0e7fe65..4677d29 100644 --- a/src/Contexts/AppContext.js +++ b/src/Contexts/AppContext.js @@ -1,7 +1,64 @@ -import React, { createContext, useState } from 'react'; +import React, { createContext, useEffect, useState } from 'react'; +import * as yup from 'yup'; + +import WebApi from '../Services/WebApi'; export const AppContext = createContext(null); +let checkPins = null; + +yup.addMethod(yup.string, 'validateColor', function(this: yup.StringSchema, name) { + return this.test('', 'Valid hex color required', (value) => value?.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i)); +}); + +yup.addMethod(yup.NumberSchema, 'validateSelectionWhenValue', function(this: yup.NumberSchema, name, choices) { + return this.when(name, { + is: value => !isNaN(parseInt(value)), + then: () => this.required().oneOf(choices.map(o => o.value)), + otherwise: () => yup.mixed().notRequired() + }) +}); + +yup.addMethod(yup.NumberSchema, 'validateNumberWhenValue', function(this: yup.NumberSchema, name) { + return this.when(name, { + is: value => !isNaN(parseInt(value)), + then: () => this.required(), + otherwise: () => yup.mixed().notRequired().strip() + }) +}); + +yup.addMethod(yup.NumberSchema, 'validateMinWhenEqualTo', function(this: yup.NumberSchema, name, compareValue, min) { + return this.when(name, { + is: value => parseInt(value) === compareValue, + then: () => this.required().min(min), + otherwise: () => yup.mixed().notRequired().strip() + }) +}); + +yup.addMethod(yup.NumberSchema, 'validateRangeWhenValue', function(this: yup.NumberSchema, name, min, max) { + return this.when(name, { + is: value => !isNaN(parseInt(value)), + then: () => this.required().min(min).max(max), + otherwise: () => yup.mixed().notRequired().strip() + }); +}); + +yup.addMethod(yup.NumberSchema, 'validatePinWhenEqualTo', function(this: yup.NumberSchema, name, compareName, compareValue) { + return this.when(compareName, { + is: value => parseInt(value) === compareValue, + then: () => this.validatePinWhenValue(name), + otherwise: () => yup.mixed().notRequired().strip() + }) +}); + +yup.addMethod(yup.NumberSchema, 'validatePinWhenValue', function(this: yup.NumberSchema, name) { + return this.checkUsedPins(); +}); + +yup.addMethod(yup.NumberSchema, 'checkUsedPins', function(this: yup.NumberSchema) { + return this.test('', '${originalValue} is unavailable/already assigned!', (value) => checkPins(value)); +}); + export const AppContextProvider = ({ children, ...props }) => { const [buttonLabels, _setButtonLabels] = useState(localStorage.getItem('buttonLabels') || 'gp2040'); const setButtonLabels = (buttonLabels) => { @@ -39,6 +96,31 @@ export const AppContextProvider = ({ children, ...props }) => { _setGradientPressedColor1(gradientPressedColor2); }; + const [usedPins, setUsedPins] = useState([]); + + const updateUsedPins = async () => { + const data = await WebApi.getUsedPins(); + setUsedPins(data.usedPins); + console.log('usedPins updated:', data.usedPins); + return data; + }; + + useEffect(() => { + updateUsedPins(); + }, []); + + useEffect(() => { + checkPins = (value) => { + const hasValue = value > -1; + const isValid = value === undefined + || value === -1 + || (hasValue && value < 30 && (usedPins || []).indexOf(value) === -1); + return isValid; + }; + }, [usedPins, setUsedPins]); + + console.log('usedPins:', usedPins); + return ( { gradientPressedColor1, gradientPressedColor2, savedColors, + usedPins, setButtonLabels, setGradientNormalColor1, setGradientNormalColor2, setGradientPressedColor1, setGradientPressedColor2, setSavedColors, + setUsedPins, + updateUsedPins, }} > {children} diff --git a/src/Pages/AddonsConfigPage.js b/src/Pages/AddonsConfigPage.js index 57d5e5e..f98d20a 100644 --- a/src/Pages/AddonsConfigPage.js +++ b/src/Pages/AddonsConfigPage.js @@ -1,7 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Button, Form, Row, FormCheck } from 'react-bootstrap'; import { Formik, useFormikContext } from 'formik'; import * as yup from 'yup'; + +import { AppContext } from '../Contexts/AppContext'; import FormControl from '../Components/FormControl'; import FormSelect from '../Components/FormSelect'; import Section from '../Components/Section'; @@ -41,9 +43,11 @@ const SHMUP_MIXED_MODES = [ ]; const ANALOG_PINS = [ - -1,26,27,28 + -1,26,27,28,29 ]; +const ANALOG_PIN_OPTIONS = ANALOG_PINS.map((i) => ); + const BUTTON_MASKS = [ { label: 'None', value: 0 }, { label: 'B1', value: (1 << 0) }, @@ -248,117 +252,85 @@ const SOCD_MODES = [ { label: 'SOCD Cleaning Off', value: 4 }, ]; -yup.NumberSchema.prototype.validateSelectionWhenEnabled = function(name, choices) { - return this.when(name, { - is: value => !!value, - then: () => this.required().oneOf(choices.map(o => o.value)), - otherwise: () => yup.mixed().notRequired() - }) -}; - -yup.NumberSchema.prototype.validateNumberWhenEnabled = function(name) { - return this.when(name, { - is: value => !!value, - then: () => this.required(), - otherwise: () => yup.mixed().notRequired().strip() - }) -}; - -yup.NumberSchema.prototype.validateRangeWhenEnabled = function(name, min, max) { - return this.when(name, { - is: value =>!!value, - then: () => this.required().min(min).max(max), - otherwise: () => yup.mixed().notRequired().strip() - }) -}; - -yup.NumberSchema.prototype.validatePinWhenEnabled = function(name) { - return this.checkUsedPins().validateRangeWhenEnabled(name, -1, 29) -}; - -yup.NumberSchema.prototype.checkUsedPins = function() { - return this.test('', '${originalValue} is unavailable/already assigned!', (value) => usedPins.indexOf(value) === -1) -}; - const schema = yup.object().shape({ I2CAnalog1219InputEnabled: yup.number().label('I2C Analog1219 Input Enabled'), - i2cAnalog1219SDAPin: yup.number().label('I2C Analog1219 SDA Pin').validatePinWhenEnabled('I2CAnalog1219InputEnabled'), - i2cAnalog1219SCLPin: yup.number().label('I2C Analog1219 SCL Pin').validatePinWhenEnabled('I2CAnalog1219InputEnabled'), - i2cAnalog1219Block: yup.number().label('I2C Analog1219 Block').validateSelectionWhenEnabled('I2CAnalog1219InputEnabled', I2C_BLOCKS), - i2cAnalog1219Speed: yup.number().label('I2C Analog1219 Speed').validateNumberWhenEnabled('I2CAnalog1219InputEnabled'), - i2cAnalog1219Address: yup.number().label('I2C Analog1219 Address').validateNumberWhenEnabled('I2CAnalog1219InputEnabled'), + i2cAnalog1219SDAPin: yup.number().label('I2C Analog1219 SDA Pin').validatePinWhenValue('I2CAnalog1219InputEnabled'), + i2cAnalog1219SCLPin: yup.number().label('I2C Analog1219 SCL Pin').validatePinWhenValue('I2CAnalog1219InputEnabled'), + i2cAnalog1219Block: yup.number().label('I2C Analog1219 Block').validateSelectionWhenValue('I2CAnalog1219InputEnabled', I2C_BLOCKS), + i2cAnalog1219Speed: yup.number().label('I2C Analog1219 Speed').validateNumberWhenValue('I2CAnalog1219InputEnabled'), + i2cAnalog1219Address: yup.number().label('I2C Analog1219 Address').validateNumberWhenValue('I2CAnalog1219InputEnabled'), AnalogInputEnabled: yup.number().required().label('Analog Input Enabled'), - analogAdcPinX: yup.number().label('Analog Stick Pin X').validatePinWhenEnabled('AnalogInputEnabled'), - analogAdcPinY: yup.number().label('Analog Stick Pin Y').validatePinWhenEnabled('AnalogInputEnabled'), + analogAdcPinX: yup.number().label('Analog Stick Pin X').validatePinWhenValue('AnalogInputEnabled'), + analogAdcPinY: yup.number().label('Analog Stick Pin Y').validatePinWhenValue('AnalogInputEnabled'), BoardLedAddonEnabled: yup.number().required().label('Board LED Add-On Enabled'), - onBoardLedMode: yup.number().label('On-Board LED Mode').validateSelectionWhenEnabled('BoardLedAddonEnabled', ON_BOARD_LED_MODES), + onBoardLedMode: yup.number().label('On-Board LED Mode').validateSelectionWhenValue('BoardLedAddonEnabled', ON_BOARD_LED_MODES), BootselButtonAddonEnabled: yup.number().required().label('Boot Select Button Add-On Enabled'), - bootselButtonMap: yup.number().label('BOOTSEL Button Map').validateSelectionWhenEnabled('BootselButtonAddonEnabled', BUTTON_MASKS), + bootselButtonMap: yup.number().label('BOOTSEL Button Map').validateSelectionWhenValue('BootselButtonAddonEnabled', BUTTON_MASKS), BuzzerSpeakerAddonEnabled: yup.number().required().label('Buzzer Speaker Add-On Enabled'), - buzzerPin: yup.number().label('Buzzer Pin').validatePinWhenEnabled('BuzzerSpeakerAddonEnabled'), - buzzerVolume: yup.number().label('Buzzer Volume').validateRangeWhenEnabled('BuzzerSpeakerAddonEnabled', 0, 100), + buzzerPin: yup.number().label('Buzzer Pin').validatePinWhenValue('BuzzerSpeakerAddonEnabled'), + buzzerVolume: yup.number().label('Buzzer Volume').validateRangeWhenValue('BuzzerSpeakerAddonEnabled', 0, 100), DualDirectionalInputEnabled: yup.number().required().label('Dual Directional Input Enabled'), - dualDirUpPin: yup.number().label('Dual Directional Up Pin').validatePinWhenEnabled('DualDirectionalInputEnabled') , - dualDirDownPin: yup.number().label('Dual Directional Down Pin').validatePinWhenEnabled('DualDirectionalInputEnabled'), - dualDirLeftPin: yup.number().label('Dual Directional Left Pin').validatePinWhenEnabled('DualDirectionalInputEnabled'), - dualDirRightPin: yup.number().label('Dual Directional Right Pin').validatePinWhenEnabled('DualDirectionalInputEnabled'), - dualDirDpadMode: yup.number().label('Dual Stick Mode').validateSelectionWhenEnabled('DualDirectionalInputEnabled', DUAL_STICK_MODES), - dualDirCombineMode: yup.number().label('Dual Combination Mode').validateSelectionWhenEnabled('DualDirectionalInputEnabled', DUAL_COMBINE_MODES), + dualDirUpPin: yup.number().label('Dual Directional Up Pin').validatePinWhenValue('DualDirectionalInputEnabled') , + dualDirDownPin: yup.number().label('Dual Directional Down Pin').validatePinWhenValue('DualDirectionalInputEnabled'), + dualDirLeftPin: yup.number().label('Dual Directional Left Pin').validatePinWhenValue('DualDirectionalInputEnabled'), + dualDirRightPin: yup.number().label('Dual Directional Right Pin').validatePinWhenValue('DualDirectionalInputEnabled'), + dualDirDpadMode: yup.number().label('Dual Stick Mode').validateSelectionWhenValue('DualDirectionalInputEnabled', DUAL_STICK_MODES), + dualDirCombineMode: yup.number().label('Dual Combination Mode').validateSelectionWhenValue('DualDirectionalInputEnabled', DUAL_COMBINE_MODES), ExtraButtonAddonEnabled: yup.number().required().label('Extra Button Add-On Enabled'), - extraButtonPin: yup.number().label('Extra Button Pin').validatePinWhenEnabled('ExtraButtonAddonEnabled'), - extraButtonMap: yup.number().label('Extra Button Map').validateSelectionWhenEnabled('ExtraButtonAddonEnabled', BUTTON_MASKS), + extraButtonPin: yup.number().label('Extra Button Pin').validatePinWhenValue('ExtraButtonAddonEnabled'), + extraButtonMap: yup.number().label('Extra Button Map').validateSelectionWhenValue('ExtraButtonAddonEnabled', BUTTON_MASKS), JSliderInputEnabled: yup.number().required().label('JSlider Input Enabled'), - sliderLSPin: yup.number().label('Slider LS Pin').validatePinWhenEnabled('JSliderInputEnabled'), - sliderRSPin: yup.number().label('Slider RS Pin').validatePinWhenEnabled('JSliderInputEnabled'), + sliderLSPin: yup.number().label('Slider LS Pin').validatePinWhenValue('JSliderInputEnabled'), + sliderRSPin: yup.number().label('Slider RS Pin').validatePinWhenValue('JSliderInputEnabled'), PlayerNumAddonEnabled: yup.number().required().label('Player Number Add-On Enabled'), - playerNumber: yup.number().label('Player Number').validateRangeWhenEnabled('PlayerNumAddonEnabled', 1, 4), + playerNumber: yup.number().label('Player Number').validateRangeWhenValue('PlayerNumAddonEnabled', 1, 4), PS4ModeAddonEnabled: yup.number().required().label('PS4 Mode Add-on Enabled'), ReverseInputEnabled: yup.number().required().label('Reverse Input Enabled'), - reversePin: yup.number().label('Reverse Pin').validatePinWhenEnabled('ReverseInputEnabled'), - reversePinLED: yup.number().label('Reverse Pin LED').validatePinWhenEnabled('ReverseInputEnabled'), + reversePin: yup.number().label('Reverse Pin').validatePinWhenValue('ReverseInputEnabled'), + reversePinLED: yup.number().label('Reverse Pin LED').validatePinWhenValue('ReverseInputEnabled'), SliderSOCDInputEnabled: yup.number().required().label('Slider SOCD Input Enabled'), - sliderSOCDModeOne: yup.number().label('SOCD Slider Mode One').validateSelectionWhenEnabled('SliderSOCDInputEnabled', SOCD_MODES), - sliderSOCDModeTwo: yup.number().label('SOCD Slider Mode Two').validateSelectionWhenEnabled('SliderSOCDInputEnabled', SOCD_MODES), - sliderSOCDModeDefault: yup.number().label('SOCD Slider Mode Default').validateSelectionWhenEnabled('SliderSOCDInputEnabled', SOCD_MODES), - sliderSOCDPinOne: yup.number().label('Slider SOCD Up Priority Pin').validatePinWhenEnabled('SliderSOCDInputEnabled'), - sliderSOCDPinTwo: yup.number().label('Slider SOCD Second Priority Pin').validatePinWhenEnabled('SliderSOCDInputEnabled'), + sliderSOCDModeOne: yup.number().label('SOCD Slider Mode One').validateSelectionWhenValue('SliderSOCDInputEnabled', SOCD_MODES), + sliderSOCDModeTwo: yup.number().label('SOCD Slider Mode Two').validateSelectionWhenValue('SliderSOCDInputEnabled', SOCD_MODES), + sliderSOCDModeDefault: yup.number().label('SOCD Slider Mode Default').validateSelectionWhenValue('SliderSOCDInputEnabled', SOCD_MODES), + sliderSOCDPinOne: yup.number().label('Slider SOCD Up Priority Pin').validatePinWhenValue('SliderSOCDInputEnabled'), + sliderSOCDPinTwo: yup.number().label('Slider SOCD Second Priority Pin').validatePinWhenValue('SliderSOCDInputEnabled'), TurboInputEnabled: yup.number().required().label('Turbo Input Enabled'), - turboPin: yup.number().label('Turbo Pin').validatePinWhenEnabled('TurboInputEnabled'), - turboPinLED: yup.number().label('Turbo Pin LED').validatePinWhenEnabled('TurboInputEnabled'), - pinShmupBtn1: yup.number().label('Charge Shot 1 Pin').validatePinWhenEnabled('TurboInputEnabled'), - pinShmupBtn2: yup.number().label('Charge Shot 2 Pin').validatePinWhenEnabled('TurboInputEnabled'), - pinShmupBtn3: yup.number().label('Charge Shot 3 Pin').validatePinWhenEnabled('TurboInputEnabled'), - pinShmupBtn4: yup.number().label('Charge Shot 4 Pin').validatePinWhenEnabled('TurboInputEnabled'), - pinShmupDial: yup.number().label('Shmup Dial Pin').validatePinWhenEnabled('TurboInputEnabled'), - turboShotCount: yup.number().label('Turbo Shot Count').validateRangeWhenEnabled('TurboInputEnabled', 5, 30), - shmupMode: yup.number().label('Shmup Mode Enabled').validateRangeWhenEnabled('TurboInputEnabled', 0, 1), - shmupMixMode: yup.number().label('Shmup Mix Priority').validateSelectionWhenEnabled('TurboInputEnabled', DUAL_STICK_MODES), - shmupAlwaysOn1: yup.number().label('Turbo-Button 1 (Always On)').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupAlwaysOn2: yup.number().label('Turbo-Button 2 (Always On)').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupAlwaysOn3: yup.number().label('Turbo-Button 3 (Always On)').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupAlwaysOn4: yup.number().label('Turbo-Button 4 (Always On)').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupBtnMask1: yup.number().label('Charge Shot Button 1 Map').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupBtnMask2: yup.number().label('Charge Shot Button 2 Map').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupBtnMask3: yup.number().label('Charge Shot Button 3 Map').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), - shmupBtnMask4: yup.number().label('Charge Shot Button 4 Map').validateSelectionWhenEnabled('TurboInputEnabled', BUTTON_MASKS), + turboPin: yup.number().label('Turbo Pin').validatePinWhenValue('TurboInputEnabled'), + turboPinLED: yup.number().label('Turbo Pin LED').validatePinWhenValue('TurboInputEnabled'), + pinShmupBtn1: yup.number().label('Charge Shot 1 Pin').validatePinWhenValue('TurboInputEnabled'), + pinShmupBtn2: yup.number().label('Charge Shot 2 Pin').validatePinWhenValue('TurboInputEnabled'), + pinShmupBtn3: yup.number().label('Charge Shot 3 Pin').validatePinWhenValue('TurboInputEnabled'), + pinShmupBtn4: yup.number().label('Charge Shot 4 Pin').validatePinWhenValue('TurboInputEnabled'), + pinShmupDial: yup.number().label('Shmup Dial Pin').validatePinWhenValue('TurboInputEnabled'), + turboShotCount: yup.number().label('Turbo Shot Count').validateRangeWhenValue('TurboInputEnabled', 5, 30), + shmupMode: yup.number().label('Shmup Mode Enabled').validateRangeWhenValue('TurboInputEnabled', 0, 1), + shmupMixMode: yup.number().label('Shmup Mix Priority').validateSelectionWhenValue('TurboInputEnabled', DUAL_STICK_MODES), + shmupAlwaysOn1: yup.number().label('Turbo-Button 1 (Always On)').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupAlwaysOn2: yup.number().label('Turbo-Button 2 (Always On)').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupAlwaysOn3: yup.number().label('Turbo-Button 3 (Always On)').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupAlwaysOn4: yup.number().label('Turbo-Button 4 (Always On)').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupBtnMask1: yup.number().label('Charge Shot Button 1 Map').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupBtnMask2: yup.number().label('Charge Shot Button 2 Map').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupBtnMask3: yup.number().label('Charge Shot Button 3 Map').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), + shmupBtnMask4: yup.number().label('Charge Shot Button 4 Map').validateSelectionWhenValue('TurboInputEnabled', BUTTON_MASKS), WiiExtensionAddonEnabled: yup.number().required().label('Wii Extensions Enabled'), - wiiExtensionSDAPin: yup.number().required().label('WiiExtension I2C SDA Pin').validatePinWhenEnabled('WiiExtensionAddonEnabled'), - wiiExtensionSCLPin: yup.number().required().label('WiiExtension I2C SCL Pin').validatePinWhenEnabled('WiiExtensionAddonEnabled'), - wiiExtensionBlock: yup.number().required().label('WiiExtension I2C Block').validateSelectionWhenEnabled('WiiExtensionAddonEnabled', I2C_BLOCKS), - wiiExtensionSpeed: yup.number().label('WiiExtension I2C Speed').validateNumberWhenEnabled('WiiExtensionAddonEnabled'), + wiiExtensionSDAPin: yup.number().required().label('WiiExtension I2C SDA Pin').validatePinWhenValue('WiiExtensionAddonEnabled'), + wiiExtensionSCLPin: yup.number().required().label('WiiExtension I2C SCL Pin').validatePinWhenValue('WiiExtensionAddonEnabled'), + wiiExtensionBlock: yup.number().required().label('WiiExtension I2C Block').validateSelectionWhenValue('WiiExtensionAddonEnabled', I2C_BLOCKS), + wiiExtensionSpeed: yup.number().label('WiiExtension I2C Speed').validateNumberWhenValue('WiiExtensionAddonEnabled'), }); const defaultValues = { @@ -429,15 +401,13 @@ const defaultValues = { WiiExtensionAddonEnabled: 0, }; -let usedPins = []; - const FormContext = ({setStoredData}) => { const { values, setValues } = useFormikContext(); useEffect(() => { async function fetchData() { const data = await WebApi.getAddonsOptions(); - usedPins = data.usedPins; + setValues(data); setStoredData(JSON.parse(JSON.stringify(data))); // Do a deep copy to keep the original } @@ -611,6 +581,7 @@ function flattenObject(object) { } export default function AddonsConfigPage() { + const { updateUsedPins } = useContext(AppContext); const [saveMessage, setSaveMessage] = useState(''); const [storedData, setStoredData] = useState({}); @@ -631,6 +602,8 @@ export default function AddonsConfigPage() { const success = await WebApi.setAddonsOptions(resultObject); setStoredData(JSON.parse(JSON.stringify(values))); // Update to reflect saved data setSaveMessage(success ? 'Saved! Please Restart Your Device' : 'Unable to Save'); + if (success) + updateUsedPins(); }; const handleCheckbox = async (name, values) => { @@ -707,7 +680,7 @@ export default function AddonsConfigPage() { @@ -796,7 +769,7 @@ export default function AddonsConfigPage() { isInvalid={errors.pinShmupDial} onChange={handleChange} > - {ANALOG_PINS.map((i) => )} + {ANALOG_PIN_OPTIONS} o.value)).label('Button Layout Left') const buttonLayoutRightSchema = yup.number().required().oneOf(BUTTON_LAYOUTS_RIGHT.map(o => o.value)).label('Button Layout Right') const schema = yup.object().shape({ enabled: yup.number().label('Enabled?'), i2cAddress: yup.string().required().label('I2C Address'), - // eslint-disable-next-line no-template-curly-in-string - sdaPin: yup.number().required().min(-1).max(29).test('', '${originalValue} is already assigned!', (value) => true).label('SDA Pin'), - // eslint-disable-next-line no-template-curly-in-string - sclPin: yup.number().required().min(-1).max(29).test('', '${originalValue} is already assigned!', (value) => true).label('SCL Pin'), + sdaPin: yup.number().label('SDA Pin').validatePinWhenValue('sdaPin'), + sclPin: yup.number().label('SCL Pin').validatePinWhenValue('sclPin'), i2cBlock: yup.number().required().oneOf(I2C_BLOCKS.map(o => o.value)).label('I2C Block'), i2cSpeed: yup.number().required().label('I2C Speed'), flipDisplay: yup.number().oneOf(DISPLAY_FLIP_MODES.map(o => o.value)).label('Flip Display'), @@ -165,7 +162,6 @@ const FormContext = () => { useEffect(() => { async function fetchData() { const data = await WebApi.getDisplayOptions(); - usedPins = data.usedPins; const splashImageResponse = await WebApi.getSplashImage(); data.splashImage = splashImageResponse.splashImage; setValues(data); @@ -231,12 +227,18 @@ const FormContext = () => { const isButtonLayoutCustom = (values) => values.buttonLayout === 12 || values.buttonLayoutRight === 16 export default function DisplayConfigPage() { + const { updateUsedPins } = useContext(AppContext); const [saveMessage, setSaveMessage] = useState(''); const onSuccess = async (values) => { const success = await WebApi.setDisplayOptions(values, false) - .then(() => WebApi.setSplashImage(values)); + .then(() => WebApi.setSplashImage(values)); + + if (success) + await updateUsedPins(); + setSaveMessage(success ? 'Saved! Please Restart Your Device' : 'Unable to Save'); + }; const onChangeCanvas = (base64, form, field) => { diff --git a/src/Pages/LEDConfigPage.js b/src/Pages/LEDConfigPage.js index 5d30b5d..3ae84db 100644 --- a/src/Pages/LEDConfigPage.js +++ b/src/Pages/LEDConfigPage.js @@ -1,15 +1,23 @@ import React, { useContext, useEffect, useState } from 'react'; -import { Button, Form, Row } from 'react-bootstrap'; +import Button from 'react-bootstrap/Button'; +import Col from 'react-bootstrap/Col'; +import Container from 'react-bootstrap/Container'; +import Form from 'react-bootstrap/Form'; +import Row from 'react-bootstrap/Row'; import { Formik, useFormikContext } from 'formik'; -import orderBy from 'lodash/orderBy'; import * as yup from 'yup'; +import orderBy from 'lodash/orderBy'; +import { SketchPicker } from '@hello-pangea/color-picker'; import { AppContext } from '../Contexts/AppContext'; +import ColorPicker from '../Components/ColorPicker'; import Section from '../Components/Section'; import DraggableListGroup from '../Components/DraggableListGroup'; import FormControl from '../Components/FormControl'; import FormSelect from '../Components/FormSelect'; import BUTTONS from '../Data/Buttons.json'; +import LEDColors from '../Data/LEDColors'; +import { hexToInt, rgbIntToHex } from '../Services/Utilities'; import WebApi from '../Services/WebApi'; const LED_FORMATS = [ @@ -25,6 +33,13 @@ const BUTTON_LAYOUTS = [ { label: 'WASD Layout', value: 2 }, ]; +const PLED_LABELS = [ + { 0: 'PLED #1 Pin', 1: 'PLED #1 Index' }, + { 0: 'PLED #2 Pin', 1: 'PLED #2 Index' }, + { 0: 'PLED #3 Pin', 1: 'PLED #3 Index' }, + { 0: 'PLED #4 Pin', 1: 'PLED #4 Index' }, +]; + const defaultValue = { brightnessMaximum: 255, brightnessSteps: 5, @@ -32,18 +47,35 @@ const defaultValue = { ledFormat: 0, ledLayout: 0, ledsPerButton: 2, + pledType: -1, + pledPin1: -1, + pledPin2: -1, + pledPin3: -1, + pledPin4: -1, + pledIndex1: -1, + pledIndex2: -1, + pledIndex3: -1, + pledIndex4: -1, + pledColor: '#00ff00', }; -let usedPins = []; - const schema = yup.object().shape({ brightnessMaximum : yup.number().required().positive().integer().min(0).max(255).label('Max Brightness'), brightnessSteps : yup.number().required().positive().integer().min(1).max(10).label('Brightness Steps'), - // eslint-disable-next-line no-template-curly-in-string - dataPin : yup.number().required().min(-1).max(29).test('', '${originalValue} is already assigned!', (value) => usedPins.indexOf(value) === -1).label('Data Pin'), + dataPin : yup.number().required().validatePinWhenValue('dataPin'), ledFormat : yup.number().required().positive().integer().min(0).max(3).label('LED Format'), ledLayout : yup.number().required().positive().integer().min(0).max(2).label('LED Layout'), - ledsPerButton : yup.number().required().positive().integer().min(1).label('LEDs Per Pixel'), + ledsPerButton : yup.number().required().positive().integer().min(1).label('LEDs Per Pixel'), + pledType : yup.number().required().label('Player LED Type'), + pledColor : yup.string().label('RGB Player LEDs').validateColor(), + pledPin1 : yup.number().label('PLED 1').validatePinWhenEqualTo('pledPins1', 'pledType', 0), + pledPin2 : yup.number().label('PLED 2').validatePinWhenEqualTo('pledPins2', 'pledType', 0), + pledPin3 : yup.number().label('PLED 3').validatePinWhenEqualTo('pledPins3', 'pledType', 0), + pledPin4 : yup.number().label('PLED 4').validatePinWhenEqualTo('pledPins4', 'pledType', 0), + pledIndex1 : yup.number().label('PLED Index 1').validateMinWhenEqualTo('pledType', 1, 0), + pledIndex2 : yup.number().label('PLED Index 2').validateMinWhenEqualTo('pledType', 1, 0), + pledIndex3 : yup.number().label('PLED Index 3').validateMinWhenEqualTo('pledType', 1, 0), + pledIndex4 : yup.number().label('PLED Index 4').validateMinWhenEqualTo('pledType', 1, 0), }); const getLedButtons = (buttonLabels, map, excludeNulls) => { @@ -73,7 +105,12 @@ const getLedMap = (buttonLabels, ledButtons, excludeNulls) => { return map; } -const FormContext = ({ buttonLabels, ledButtonMap, ledFormat, setDataSources }) => { +const FormContext = ({ + buttonLabels, ledButtonMap, ledFormat, pledColor, pledType, + pledPin1, pledPin2, pledPin3, pledPin4, + pledIndex1, pledIndex2, pledIndex3, pledIndex4, + setDataSources +}) => { const { setFieldValue, setValues } = useFormikContext(); useEffect(() => { @@ -94,7 +131,12 @@ const FormContext = ({ buttonLabels, ledButtonMap, ledFormat, setDataSources }) getLedButtons(buttonLabels, available, true), getLedButtons(buttonLabels, assigned, true), ]; - usedPins = data.usedPins; + + data.pledIndex1 = data.pledType === 1 ? data.pledPin1 : -1; + data.pledIndex2 = data.pledType === 1 ? data.pledPin2 : -1; + data.pledIndex3 = data.pledType === 1 ? data.pledPin3 : -1; + data.pledIndex4 = data.pledType === 1 ? data.pledPin4 : -1; + setDataSources(dataSources); setValues(data); } @@ -105,42 +147,146 @@ const FormContext = ({ buttonLabels, ledButtonMap, ledFormat, setDataSources }) if (!!ledFormat) setFieldValue('ledFormat', parseInt(ledFormat)); }, [ledFormat, setFieldValue]); - + useEffect(() => { setFieldValue('ledButtonMap', ledButtonMap); }, [ledButtonMap, setFieldValue]); + + useEffect(() => { + if (!!pledPin1) + setFieldValue('pledPin1', parseInt(pledPin1)); + }, [pledPin1, setFieldValue]); + useEffect(() => { + if (!!pledPin2) + setFieldValue('pledPin2', parseInt(pledPin2)); + }, [pledPin2, setFieldValue]); + useEffect(() => { + if (!!pledPin3) + setFieldValue('pledPin3', parseInt(pledPin3)); + }, [pledPin3, setFieldValue]); + useEffect(() => { + if (!!pledPin4) + setFieldValue('pledPin4', parseInt(pledPin4)); + }, [pledPin4, setFieldValue]); + useEffect(() => { + if (!!pledIndex1) + setFieldValue('pledIndex1', parseInt(pledIndex1)); + }, [pledIndex1, setFieldValue]); + useEffect(() => { + if (!!pledIndex2) + setFieldValue('pledIndex2', parseInt(pledIndex2)); + }, [pledIndex2, setFieldValue]); + useEffect(() => { + if (!!pledIndex3) + setFieldValue('pledIndex3', parseInt(pledIndex3)); + }, [pledIndex3, setFieldValue]); + useEffect(() => { + if (!!pledIndex4) + setFieldValue('pledIndex4', parseInt(pledIndex4)); + }, [pledIndex4, setFieldValue]); + useEffect(() => { + if (!!pledColor) + setFieldValue('pledColor', pledColor); + }, [pledColor, setFieldValue]); return null; }; export default function LEDConfigPage() { - const { buttonLabels } = useContext(AppContext); + const { buttonLabels, updateUsedPins } = useContext(AppContext); const [saveMessage, setSaveMessage] = useState(''); const [ledButtonMap, setLedButtonMap] = useState([]); const [dataSources, setDataSources] = useState([[], []]); + const [colorPickerTarget, setColorPickerTarget] = useState(null); + const [showPicker, setShowPicker] = useState(false); + const [rgbLedStartIndex, setRgbLedStartIndex] = useState(0); - const ledOrderChanged = (ledOrderArrays) => { - if (ledOrderArrays.length === 2) + const ledOrderChanged = (ledOrderArrays, ledsPerButton) => { + if (ledOrderArrays.length === 2) { setLedButtonMap(getLedMap(buttonLabels, ledOrderArrays[1])); + setRgbLedStartIndex(ledOrderArrays[1].length * (ledsPerButton || 0)); + console.log('new start index: ', ledOrderArrays[1].length * (ledsPerButton || 0), ledOrderArrays); + } + }; + + const ledsPerButtonChanged = (e, handleChange) => { + const ledsPerButton = parseInt(e.target.value); + setRgbLedStartIndex(dataSources[1].length * (ledsPerButton || 0)); + handleChange(e); + }; + + const setPledColor = (values, hexColor) => { + values.pledColor = hexColor; + }; + + const showRgbPledPicker = (e) => { + setColorPickerTarget(e.target); + setShowPicker(true); + }; + + const toggleRgbPledPicker = (e) => { + e.stopPropagation(); + setColorPickerTarget(e.target); + setShowPicker(!showPicker); }; const onSuccess = async (values) => { - const success = await WebApi.setLedOptions(values); + const data = { ...values }; + data.pledType = parseInt(values.pledType); + if (data.pledColor) + data.pledColor = hexToInt(values.pledColor); + + const success = await WebApi.setLedOptions(data); + if (success) + updateUsedPins(); + setSaveMessage(success ? 'Saved! Please Restart Your Device' : 'Unable to Save'); }; + const onSubmit = (e, handleSubmit, setValues, values) => { + setSaveMessage(''); + e.preventDefault(); + + values.pledType = parseInt(values.pledType); + + // Consolidate PLED fields based on selected type + switch (values.pledType) { + case 0: + // PLED pin already set + break; + + case 1: + values.pledPin1 = values.pledIndex1; + values.pledPin2 = values.pledIndex2; + values.pledPin3 = values.pledIndex3; + values.pledPin4 = values.pledIndex4; + break; + + default: + values.pledPin1 = -1; + values.pledPin2 = -1; + values.pledPin3 = -1; + values.pledPin4 = -1; + break; + } + + setValues(values); + handleSubmit(); + }; + return ( {({ handleSubmit, handleChange, handleBlur, + setValues, values, touched, errors, }) => ( -
-
+ onSubmit(e, handleSubmit, setValues, values)}> +
ledsPerButtonChanged(e, handleChange)} min={1} />
-
+
+ + + + + + + + + + + +
+

Here you can define which buttons have RGB LEDs and in what order they run from the control board. This is required for certain LED animations and static theme support. @@ -229,7 +519,7 @@ export default function LEDConfigPage() { groupName="test" titles={['Available Buttons', 'Assigned Buttons']} dataSources={dataSources} - onChange={ledOrderChanged} + onChange={(a) => ledOrderChanged(a, values.ledsPerButton)} />

diff --git a/src/Pages/PinMapping.js b/src/Pages/PinMapping.js index b01e2be..552947f 100644 --- a/src/Pages/PinMapping.js +++ b/src/Pages/PinMapping.js @@ -12,11 +12,12 @@ const requiredButtons = ['S2']; const errorType = { required: 'required', conflict: 'conflict', - invalid: 'invalid' + invalid: 'invalid', + used: 'used', }; export default function PinMappingPage() { - const { buttonLabels } = useContext(AppContext); + const { buttonLabels, usedPins } = useContext(AppContext); const [validated, setValidated] = useState(false); const [saveMessage, setSaveMessage] = useState(''); const [buttonMappings, setButtonMappings] = useState(baseButtonMappings); @@ -71,6 +72,7 @@ export default function PinMappingPage() { const uniquePins = mappedPins.filter((p, i, a) => a.indexOf(p) === i); const conflictedPins = Object.keys(mappedPinCounts).filter(p => mappedPinCounts[p] > 1).map(parseInt); const invalidPins = uniquePins.filter(p => boards[selectedBoard].invalidPins.indexOf(p) > -1); + const otherPins = usedPins.filter(p => uniquePins.indexOf(p) === -1); for (let button of buttons) { mappings[button].error = ''; @@ -86,6 +88,10 @@ export default function PinMappingPage() { // Identify invalid pin assignments else if (invalidPins.indexOf(mappings[button].pin) > -1) mappings[button].error = errorType.invalid; + + // Identify used pins + else if (otherPins.indexOf(mappings[button].pin) > -1) + mappings[button].error = errorType.used; } setButtonMappings(mappings); @@ -107,6 +113,9 @@ export default function PinMappingPage() { else if (buttonMappings[button].error === errorType.invalid) { return {`Pin ${buttonMappings[button].pin} is invalid for this board`}; } + else if (buttonMappings[button].error === errorType.used) { + return {`Pin ${buttonMappings[button].pin} is already assigned to another feature`}; + } return <>; }; diff --git a/src/Pages/SettingsPage.js b/src/Pages/SettingsPage.js index af1b114..557479e 100644 --- a/src/Pages/SettingsPage.js +++ b/src/Pages/SettingsPage.js @@ -3,6 +3,7 @@ import { AppContext } from '../Contexts/AppContext'; import { Button, Form } from 'react-bootstrap'; import { Formik, useFormikContext } from 'formik'; import * as yup from 'yup'; + import Section from '../Components/Section'; import WebApi from '../Services/WebApi'; import BUTTONS from '../Data/Buttons.json'; diff --git a/src/Services/Utilities.js b/src/Services/Utilities.js index a1f3397..f19b394 100644 --- a/src/Services/Utilities.js +++ b/src/Services/Utilities.js @@ -1,6 +1,6 @@ // Convert a hex string to a number const hexToInt = (hex) => { - return parseInt(hex, 16); + return parseInt(hex.replace('#', ''), 16); }; // Convert a number to hex @@ -8,13 +8,13 @@ const intToHex = (d) => { return ("0"+(Number(d).toString(16))).slice(-2).toLowerCase(); }; -// Convert a 32-bit ARGB value to hex format (no # prefix) +// Convert a 32-bit ARGB value to hex format const rgbIntToHex = (rgbInt) => { let r = (rgbInt >> 16) & 255; let g = (rgbInt >> 8) & 255; let b = (rgbInt >> 0) & 255; - return `${intToHex(r)}${intToHex(g)}${intToHex(b)}`; + return `#${intToHex(r)}${intToHex(g)}${intToHex(b)}`; } // Takes an array of 8-bit RGB values and returns the hex value @@ -31,9 +31,23 @@ const rgbArrayToHex = (values) => { return `#${intToHex(r)}${intToHex(g)}${intToHex(b)}`; }; +const rgbWheel = (pos) => { + pos = 255 - pos; + if (pos < 85) { + return rgbArrayToHex([255 - pos * 3, 0, pos * 3]); + } else if (pos < 170) { + pos -= 85; + return rgbArrayToHex([0, pos * 3, 255 - pos * 3]); + } else { + pos -= 170; + return rgbArrayToHex([pos * 3, 255 - pos * 3, 0]); + } +}; + export { hexToInt, intToHex, rgbArrayToHex, rgbIntToHex, + rgbWheel, }; diff --git a/src/Services/WebApi.js b/src/Services/WebApi.js index 0aa87e5..dd24079 100644 --- a/src/Services/WebApi.js +++ b/src/Services/WebApi.js @@ -108,6 +108,15 @@ async function getLedOptions() { return axios.get(`${baseUrl}/api/getLedOptions`) .then((response) => { console.log(response.data); + + response.data.pledColor = rgbIntToHex(response.data.pledColor) || "#ffffff"; + if (response.data.pledType === 1) { + response.data.pledIndex1 = response.data.pledPin1; + response.data.pledIndex2 = response.data.pledPin2; + response.data.pledIndex3 = response.data.pledPin3; + response.data.pledIndex4 = response.data.pledPin4; + } + return response.data; }) .catch(console.error); @@ -115,6 +124,7 @@ async function getLedOptions() { async function setLedOptions(options) { let data = sanitizeRequest(options); + return axios.post(`${baseUrl}/api/setLedOptions`, sanitizeRequest(options)) .then((response) => { @@ -137,8 +147,8 @@ async function getCustomTheme() { .filter(p => p !== 'enabled') .forEach((button) => { data.customTheme[button] = { - normal: `#${rgbIntToHex(response.data[button].u)}`, - pressed: `#${rgbIntToHex(response.data[button].d)}`, + normal: rgbIntToHex(response.data[button].u), + pressed: rgbIntToHex(response.data[button].d), }; }); @@ -266,6 +276,12 @@ async function getMemoryReport() { .catch(console.error); } +async function getUsedPins() { + return axios.get(`${baseUrl}/api/getUsedPins`) + .then((response) => response.data) + .catch(console.error); +} + async function reboot(bootMode) { return axios.post(`${baseUrl}/api/reboot`, { bootMode }) .then((response) => response.data) @@ -274,6 +290,10 @@ async function reboot(bootMode) { function sanitizeRequest(request) { const newRequest = {...request}; + delete newRequest.pledIndex1; + delete newRequest.pledIndex2; + delete newRequest.pledIndex3; + delete newRequest.pledIndex4; delete newRequest.usedPins; return newRequest; } @@ -299,6 +319,7 @@ const WebApi = { setSplashImage, getFirmwareVersion, getMemoryReport, + getUsedPins, reboot }; diff --git a/src/index.scss b/src/index.scss index 85da99a..ff062bf 100644 --- a/src/index.scss +++ b/src/index.scss @@ -33,6 +33,19 @@ input.form-control { } } +button { + display: flex; + flex-direction: row; + align-items: center; + flex: 0 0 auto; + width: auto !important; + + svg { + margin: 0.25rem; + transform: translateY(-0.125rem); + } +} + // Fix for invalid inputs showing valid style after calling `form.checkValidity()` .was-validated .form-control.is-invalid { border-color: $danger;