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}
+
+ {label}
{error}
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,
}) => (
-