Format all web code (#436)
* Change editorconfig end_of_line to lf
* Running prettier with editorconfig:
npx prettier --write "{www/src,www/server}/**/*.{tsx,ts,jsx,js,json,scss}"
* Run prettier again after merging main
* Change to use single quotes in js/ts
* Run prettier again after changing to single quotes
* Add format command for those not running prettier in editor
* Format after resolving conflict
* Format after resolving conflicts
This commit is contained in:
@@ -6,7 +6,7 @@ root = true
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
end_of_line = crlf
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -18,3 +18,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[*.scss]
|
||||
end_of_line = lf
|
||||
|
||||
[*.{js,jsx,ts,tsx}]
|
||||
quote_type = single
|
||||
|
||||
16
www/package-lock.json
generated
16
www/package-lock.json
generated
@@ -38,6 +38,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.1",
|
||||
"express": "^4.18.2",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "^3.0.1",
|
||||
"sass": "^1.62.1",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^4.3.9"
|
||||
@@ -4204,6 +4205,21 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz",
|
||||
"integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"build": "vite build && npm run makefsdata",
|
||||
"dev-server": "nodemon -r dotenv/config ./server/app.js",
|
||||
"dev": "concurrently --kill-others \"npm run dev-server\" \"npm start\"",
|
||||
"format": "prettier --write \"{src,server}/**/*.{tsx,ts,jsx,js,json,scss}\"",
|
||||
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"makefsdata": "node makefsdata.js",
|
||||
"start": "vite"
|
||||
@@ -43,6 +44,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.1",
|
||||
"express": "^4.18.2",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "^3.0.1",
|
||||
"sass": "^1.62.1",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^4.3.9"
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
* GP2040-CE Configurator Development Server
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import mapValues from "lodash/mapValues.js";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { DEFAULT_KEYBOARD_MAPPING } from "../src/Data/Keyboard.js";
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import mapValues from 'lodash/mapValues.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { DEFAULT_KEYBOARD_MAPPING } from '../src/Data/Keyboard.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const { pico: picoController } = JSON.parse(
|
||||
readFileSync(path.resolve(__dirname, "../src/Data/Controllers.json"), "utf8")
|
||||
readFileSync(path.resolve(__dirname, '../src/Data/Controllers.json'), 'utf8'),
|
||||
);
|
||||
|
||||
const port = process.env.PORT || 8080;
|
||||
@@ -23,24 +23,24 @@ const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
console.log("Request:", req.method, req.url);
|
||||
console.log('Request:', req.method, req.url);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get("/api/getUsedPins", (req, res) => {
|
||||
app.get('/api/getUsedPins', (req, res) => {
|
||||
return res.send({ usedPins: Object.values(picoController) });
|
||||
});
|
||||
|
||||
app.get("/api/resetSettings", (req, res) => {
|
||||
app.get('/api/resetSettings', (req, res) => {
|
||||
return res.send({ success: true });
|
||||
});
|
||||
|
||||
app.get("/api/getDisplayOptions", (req, res) => {
|
||||
app.get('/api/getDisplayOptions', (req, res) => {
|
||||
const data = {
|
||||
enabled: 1,
|
||||
sdaPin: 0,
|
||||
sclPin: 1,
|
||||
i2cAddress: "0x3D",
|
||||
i2cAddress: '0x3D',
|
||||
i2cBlock: 0,
|
||||
i2cSpeed: 400000,
|
||||
flipDisplay: 0,
|
||||
@@ -70,19 +70,19 @@ app.get("/api/getDisplayOptions", (req, res) => {
|
||||
|
||||
displaySaverTimeout: 0,
|
||||
};
|
||||
console.log("data", data);
|
||||
console.log('data', data);
|
||||
return res.send(data);
|
||||
});
|
||||
|
||||
app.get("/api/getSplashImage", (req, res) => {
|
||||
app.get('/api/getSplashImage', (req, res) => {
|
||||
const data = {
|
||||
splashImage: Array(16 * 64).fill(255),
|
||||
};
|
||||
console.log("data", data);
|
||||
console.log('data', data);
|
||||
return res.send(data);
|
||||
});
|
||||
|
||||
app.get("/api/getGamepadOptions", (req, res) => {
|
||||
app.get('/api/getGamepadOptions', (req, res) => {
|
||||
return res.send({
|
||||
dpadMode: 0,
|
||||
inputMode: 4,
|
||||
@@ -97,67 +97,67 @@ app.get("/api/getGamepadOptions", (req, res) => {
|
||||
hotkey01: {
|
||||
auxMask: 32768,
|
||||
buttonsMask: 66304,
|
||||
action: 4
|
||||
action: 4,
|
||||
},
|
||||
hotkey02: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 131840,
|
||||
action: 1
|
||||
action: 1,
|
||||
},
|
||||
hotkey03: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 262912,
|
||||
action: 2
|
||||
action: 2,
|
||||
},
|
||||
hotkey04: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 525056,
|
||||
action: 3
|
||||
action: 3,
|
||||
},
|
||||
hotkey05: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 70144,
|
||||
action: 6
|
||||
action: 6,
|
||||
},
|
||||
hotkey06: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 135680,
|
||||
action: 7
|
||||
action: 7,
|
||||
},
|
||||
hotkey07: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 266752,
|
||||
action: 8
|
||||
action: 8,
|
||||
},
|
||||
hotkey08: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 528896,
|
||||
action: 10
|
||||
action: 10,
|
||||
},
|
||||
hotkey09: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 0,
|
||||
action: 0
|
||||
action: 0,
|
||||
},
|
||||
hotkey10: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 0,
|
||||
action: 0
|
||||
action: 0,
|
||||
},
|
||||
hotkey11: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 0,
|
||||
action: 0
|
||||
action: 0,
|
||||
},
|
||||
hotkey12: {
|
||||
auxMask: 0,
|
||||
buttonsMask: 0,
|
||||
action: 0
|
||||
}
|
||||
action: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/getLedOptions", (req, res) => {
|
||||
app.get('/api/getLedOptions', (req, res) => {
|
||||
return res.send({
|
||||
brightnessMaximum: 255,
|
||||
brightnessSteps: 5,
|
||||
@@ -195,8 +195,8 @@ app.get("/api/getLedOptions", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/getCustomTheme", (req, res) => {
|
||||
console.log("/api/getCustomTheme");
|
||||
app.get('/api/getCustomTheme', (req, res) => {
|
||||
console.log('/api/getCustomTheme');
|
||||
return res.send({
|
||||
enabled: true,
|
||||
Up: { u: 16711680, d: 255 },
|
||||
@@ -220,60 +220,64 @@ app.get("/api/getCustomTheme", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/getPinMappings", (req, res) => {
|
||||
app.get('/api/getPinMappings', (req, res) => {
|
||||
return res.send(picoController);
|
||||
});
|
||||
|
||||
app.get("/api/getKeyMappings", (req, res) =>
|
||||
res.send(mapValues(DEFAULT_KEYBOARD_MAPPING))
|
||||
app.get('/api/getKeyMappings', (req, res) =>
|
||||
res.send(mapValues(DEFAULT_KEYBOARD_MAPPING)),
|
||||
);
|
||||
|
||||
app.get("/api/getProfileOptions", (req, res) => {
|
||||
app.get('/api/getProfileOptions', (req, res) => {
|
||||
return res.send({
|
||||
alternativePinMappings: [{
|
||||
B1: 10,
|
||||
B2: 6,
|
||||
B3: 11,
|
||||
B4: 12,
|
||||
L1: 13,
|
||||
R1: 9,
|
||||
L2: 7,
|
||||
R2: 8,
|
||||
Up: 2,
|
||||
Down: 3,
|
||||
Left: 5,
|
||||
Right: 4
|
||||
},{
|
||||
B1: 10,
|
||||
B2: 11,
|
||||
B3: 12,
|
||||
B4: 13,
|
||||
L1: 6,
|
||||
R1: 8,
|
||||
L2: 7,
|
||||
R2: 9,
|
||||
Up: 3,
|
||||
Down: 2,
|
||||
Left: 4,
|
||||
Right: 5
|
||||
},{
|
||||
B1: 6,
|
||||
B2: 7,
|
||||
B3: 8,
|
||||
B4: 9,
|
||||
L1: 10,
|
||||
R1: 12,
|
||||
L2: 11,
|
||||
R2: 13,
|
||||
Up: 3,
|
||||
Down: 5,
|
||||
Left: 4,
|
||||
Right: 2
|
||||
}]
|
||||
alternativePinMappings: [
|
||||
{
|
||||
B1: 10,
|
||||
B2: 6,
|
||||
B3: 11,
|
||||
B4: 12,
|
||||
L1: 13,
|
||||
R1: 9,
|
||||
L2: 7,
|
||||
R2: 8,
|
||||
Up: 2,
|
||||
Down: 3,
|
||||
Left: 5,
|
||||
Right: 4,
|
||||
},
|
||||
{
|
||||
B1: 10,
|
||||
B2: 11,
|
||||
B3: 12,
|
||||
B4: 13,
|
||||
L1: 6,
|
||||
R1: 8,
|
||||
L2: 7,
|
||||
R2: 9,
|
||||
Up: 3,
|
||||
Down: 2,
|
||||
Left: 4,
|
||||
Right: 5,
|
||||
},
|
||||
{
|
||||
B1: 6,
|
||||
B2: 7,
|
||||
B3: 8,
|
||||
B4: 9,
|
||||
L1: 10,
|
||||
R1: 12,
|
||||
L2: 11,
|
||||
R2: 13,
|
||||
Up: 3,
|
||||
Down: 5,
|
||||
Left: 4,
|
||||
Right: 2,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/getAddonsOptions", (req, res) => {
|
||||
app.get('/api/getAddonsOptions', (req, res) => {
|
||||
return res.send({
|
||||
turboPin: -1,
|
||||
turboPinLED: -1,
|
||||
@@ -390,13 +394,13 @@ app.get("/api/getAddonsOptions", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/getFirmwareVersion", (req, res) => {
|
||||
app.get('/api/getFirmwareVersion', (req, res) => {
|
||||
return res.send({
|
||||
version: process.env.VITE_CURRENT_VERSION,
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/getButtonLayoutCustomOptions", (req, res) => {
|
||||
app.get('/api/getButtonLayoutCustomOptions', (req, res) => {
|
||||
return res.send({
|
||||
params: {
|
||||
layout: 2,
|
||||
@@ -415,11 +419,11 @@ app.get("/api/getButtonLayoutCustomOptions", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/reboot", (req, res) => {
|
||||
app.get('/api/reboot', (req, res) => {
|
||||
return res.send({});
|
||||
});
|
||||
|
||||
app.get("/api/getMemoryReport", (req, res) => {
|
||||
app.get('/api/getMemoryReport', (req, res) => {
|
||||
return res.send({
|
||||
totalFlash: 2048,
|
||||
usedFlash: 1048,
|
||||
@@ -429,7 +433,7 @@ app.get("/api/getMemoryReport", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/*", (req, res) => {
|
||||
app.post('/api/*', (req, res) => {
|
||||
console.log(req.body);
|
||||
return res.send(req.body);
|
||||
});
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
"_postman_variable_scope": "environment",
|
||||
"_postman_exported_at": "2021-11-09T21:30:14.170Z",
|
||||
"_postman_exported_using": "Postman/9.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,8 @@
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/getGamepadOptions",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"getGamepadOptions"
|
||||
]
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "getGamepadOptions"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
@@ -30,13 +25,8 @@
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/getPinMappings",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"getPinMappings"
|
||||
]
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "getPinMappings"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
@@ -48,13 +38,8 @@
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/getAddonsOptions",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"getAddonsOptions"
|
||||
]
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "getAddonsOptions"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
@@ -66,13 +51,8 @@
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/resetSettings",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"resetSettings"
|
||||
]
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "resetSettings"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
@@ -93,13 +73,8 @@
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/setGamepadOptions",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"setGamepadOptions"
|
||||
]
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "setGamepadOptions"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
@@ -120,13 +95,8 @@
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{baseUrl}}/api/setGamepadOptions",
|
||||
"host": [
|
||||
"{{baseUrl}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"setGamepadOptions"
|
||||
]
|
||||
"host": ["{{baseUrl}}"],
|
||||
"path": ["api", "setGamepadOptions"]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
@@ -137,19 +107,15 @@
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
"exec": [""]
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
"exec": [""]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { AppContextProvider } from './Contexts/AppContext';
|
||||
|
||||
import Navigation from './Components/Navigation'
|
||||
import Navigation from './Components/Navigation';
|
||||
|
||||
import HomePage from './Pages/HomePage'
|
||||
import PinMappingPage from "./Pages/PinMapping";
|
||||
import ProfileSettingsPage from "./Pages/ProfileSettings";
|
||||
import KeyboardMappingPage from "./Pages/KeyboardMapping";
|
||||
import HomePage from './Pages/HomePage';
|
||||
import PinMappingPage from './Pages/PinMapping';
|
||||
import ProfileSettingsPage from './Pages/ProfileSettings';
|
||||
import KeyboardMappingPage from './Pages/KeyboardMapping';
|
||||
import ResetSettingsPage from './Pages/ResetSettingsPage';
|
||||
import SettingsPage from './Pages/SettingsPage';
|
||||
import DisplayConfigPage from './Pages/DisplayConfig';
|
||||
@@ -45,6 +45,6 @@ const App = () => {
|
||||
</Router>
|
||||
</AppContextProvider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -14,12 +14,25 @@ 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 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 ColorPicker = ({
|
||||
types,
|
||||
onChange,
|
||||
onDismiss,
|
||||
pickerOnly,
|
||||
placement,
|
||||
show,
|
||||
target,
|
||||
title,
|
||||
...props
|
||||
}) => {
|
||||
const { savedColors, setSavedColors } = useContext(AppContext);
|
||||
const [colorPalette, setColorPalette] = useState([...ledColors, ...customColors(savedColors)]);
|
||||
const [colorPalette, setColorPalette] = useState([
|
||||
...ledColors,
|
||||
...customColors(savedColors),
|
||||
]);
|
||||
const [colorTypes, setColorTypes] = useState(types);
|
||||
const [selectedColor, setSelectedColor] = useState('#000000');
|
||||
const [selectedColorType, setSelectedColorType] = useState(types[0]);
|
||||
@@ -28,8 +41,7 @@ const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show,
|
||||
|
||||
const deleteCurrentColor = () => {
|
||||
const colorIndex = savedColors.indexOf(selectedColor);
|
||||
if (colorIndex < 0)
|
||||
return;
|
||||
if (colorIndex < 0) return;
|
||||
|
||||
const newColors = [...savedColors];
|
||||
newColors.splice(colorIndex, 1);
|
||||
@@ -38,7 +50,10 @@ const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show,
|
||||
};
|
||||
|
||||
const saveCurrentColor = () => {
|
||||
if (!selectedColor || colorPalette.filter(c => c.color === selectedColor).length > 0)
|
||||
if (
|
||||
!selectedColor ||
|
||||
colorPalette.filter((c) => c.color === selectedColor).length > 0
|
||||
)
|
||||
return;
|
||||
|
||||
const newColors = [...savedColors];
|
||||
@@ -48,8 +63,7 @@ const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show,
|
||||
};
|
||||
|
||||
const selectColor = (c, e) => {
|
||||
if (onChange)
|
||||
onChange(c.hex, e);
|
||||
if (onChange) onChange(c.hex, e);
|
||||
|
||||
selectedColorType.value = c.hex;
|
||||
|
||||
@@ -76,8 +90,9 @@ const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show,
|
||||
<Container className="color-picker">
|
||||
<h6 className="text-center">{title}</h6>
|
||||
<Row>
|
||||
{colorTypes.map(((o, i) =>
|
||||
<Form.Group as={Col}
|
||||
{colorTypes.map((o, i) => (
|
||||
<Form.Group
|
||||
as={Col}
|
||||
key={`colorType${i}`}
|
||||
className={`${o === selectedColorType ? 'selected' : ''}`}
|
||||
onClick={() => setSelectedColorType(o)}
|
||||
@@ -86,8 +101,7 @@ const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show,
|
||||
<div
|
||||
className={`color color-normal`}
|
||||
style={{ backgroundColor: o.value }}
|
||||
>
|
||||
</div>
|
||||
></div>
|
||||
</Form.Group>
|
||||
))}
|
||||
</Row>
|
||||
@@ -103,13 +117,17 @@ const ColorPicker = ({ types, onChange, onDismiss, pickerOnly, placement, show,
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="button-group d-flex justify-content-between mt-2">
|
||||
<Button size="sm" onClick={() => saveCurrentColor()}>{t('Common:button-save-color-label')}</Button>
|
||||
<Button size="sm" onClick={() => deleteCurrentColor()}>{t('Common:button-delete-color-label')}</Button>
|
||||
<Button size="sm" onClick={() => saveCurrentColor()}>
|
||||
{t('Common:button-save-color-label')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => deleteCurrentColor()}>
|
||||
{t('Common:button-delete-color-label')}
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</Popover>
|
||||
</Overlay>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ColorPicker;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #DEDEDE;
|
||||
background-color: #dedede;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,4 @@
|
||||
.sketch-picker {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import React, { useContext } from "react";
|
||||
import { Dropdown } from "react-bootstrap";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import React, { useContext } from 'react';
|
||||
import { Dropdown } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AppContext } from "../Contexts/AppContext";
|
||||
import SunIcon from "../Icons/Sun";
|
||||
import MoonStarsIcon from "../Icons/MoonStars";
|
||||
import CircleHalfIcon from "../Icons/CircleHalf";
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import SunIcon from '../Icons/Sun';
|
||||
import MoonStarsIcon from '../Icons/MoonStars';
|
||||
import CircleHalfIcon from '../Icons/CircleHalf';
|
||||
|
||||
const dropdownOptions = [
|
||||
{ scheme: "light", icon: SunIcon },
|
||||
{ scheme: "dark", icon: MoonStarsIcon },
|
||||
{ scheme: "auto", icon: CircleHalfIcon },
|
||||
{ scheme: 'light', icon: SunIcon },
|
||||
{ scheme: 'dark', icon: MoonStarsIcon },
|
||||
{ scheme: 'auto', icon: CircleHalfIcon },
|
||||
];
|
||||
|
||||
const setTheme = function (theme) {
|
||||
const rootElement = document.documentElement;
|
||||
const prefersDarkMode = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
'(prefers-color-scheme: dark)',
|
||||
).matches;
|
||||
|
||||
if (theme === "auto") {
|
||||
if (theme === 'auto') {
|
||||
rootElement.setAttribute(
|
||||
"data-bs-theme",
|
||||
prefersDarkMode ? "dark" : "light"
|
||||
'data-bs-theme',
|
||||
prefersDarkMode ? 'dark' : 'light',
|
||||
);
|
||||
} else {
|
||||
rootElement.setAttribute("data-bs-theme", theme);
|
||||
rootElement.setAttribute('data-bs-theme', theme);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ const ColorScheme = () => {
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle variant="secondary" style={{ marginRight: "7px" }}>
|
||||
<Dropdown.Toggle variant="secondary" style={{ marginRight: '7px' }}>
|
||||
<MoonStarsIcon />
|
||||
</Dropdown.Toggle>
|
||||
|
||||
@@ -56,8 +56,8 @@ const ColorScheme = () => {
|
||||
{translatedDropdownOptions.map((option) => (
|
||||
<Dropdown.Item
|
||||
key={option.theme}
|
||||
as={"button"}
|
||||
className={savedColorScheme === option.scheme ? "active" : ""}
|
||||
as={'button'}
|
||||
className={savedColorScheme === option.scheme ? 'active' : ''}
|
||||
onClick={() => setThemeAndState(option.scheme)}
|
||||
>
|
||||
<option.icon /> {option.label}
|
||||
|
||||
@@ -6,7 +6,8 @@ const DangerSection = ({ className, titleClassName, ...props }) => {
|
||||
<Section
|
||||
className={`border-danger ${className}`}
|
||||
titleClassName={`text-white bg-danger ${titleClassName}`}
|
||||
{...props} />
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
||||
import './DraggableListGroup.scss'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import './DraggableListGroup.scss';
|
||||
|
||||
const reorder = (list, startIndex, endIndex) => {
|
||||
const result = Array.from(list);
|
||||
@@ -24,31 +24,46 @@ const move = (source, destination, droppableSource, droppableDestination) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const DraggableListGroup = ({ groupName, titles, dataSources, onChange, ...props }) => {
|
||||
const DraggableListGroup = ({
|
||||
groupName,
|
||||
titles,
|
||||
dataSources,
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
const [droppableIds, setDroppableIds] = useState([]);
|
||||
const [listData, setListData] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange)
|
||||
onChange(Object.keys(listData).reduce((p, n) => { p.push(listData[n]); return p; }, []));
|
||||
onChange(
|
||||
Object.keys(listData).reduce((p, n) => {
|
||||
p.push(listData[n]);
|
||||
return p;
|
||||
}, []),
|
||||
);
|
||||
}, [listData]);
|
||||
|
||||
useEffect(() => {
|
||||
setDroppableIds(dataSources.map((v, i) => `${groupName}-${i}`));
|
||||
setListData(dataSources.reduce((p, n) => ({ ...p, [`${groupName}-${dataSources.indexOf(n)}`]: n }), {}));
|
||||
setListData(
|
||||
dataSources.reduce(
|
||||
(p, n) => ({ ...p, [`${groupName}-${dataSources.indexOf(n)}`]: n }),
|
||||
{},
|
||||
),
|
||||
);
|
||||
}, [dataSources, setDroppableIds, setListData]);
|
||||
|
||||
const onDragEnd = result => {
|
||||
const onDragEnd = (result) => {
|
||||
const { source, destination } = result;
|
||||
|
||||
if (!destination)
|
||||
return;
|
||||
if (!destination) return;
|
||||
|
||||
if (source.droppableId === destination.droppableId) {
|
||||
const items = reorder(
|
||||
listData[source.droppableId],
|
||||
source.index,
|
||||
destination.index
|
||||
destination.index,
|
||||
);
|
||||
|
||||
const newListData = { ...listData };
|
||||
@@ -59,7 +74,7 @@ const DraggableListGroup = ({ groupName, titles, dataSources, onChange, ...props
|
||||
listData[source.droppableId],
|
||||
listData[destination.droppableId],
|
||||
source,
|
||||
destination
|
||||
destination,
|
||||
);
|
||||
|
||||
const newListData = { ...listData };
|
||||
@@ -72,7 +87,7 @@ const DraggableListGroup = ({ groupName, titles, dataSources, onChange, ...props
|
||||
return (
|
||||
<div className="draggable-list-group">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{droppableIds.map((droppableId, i) =>
|
||||
{droppableIds.map((droppableId, i) => (
|
||||
<div key={droppableId} className="draggable-list-container">
|
||||
<div className="draggable-list-title">{titles[i]}</div>
|
||||
<Droppable key={droppableId} droppableId={droppableId}>
|
||||
@@ -80,20 +95,24 @@ const DraggableListGroup = ({ groupName, titles, dataSources, onChange, ...props
|
||||
<div
|
||||
{...droppableProvided.droppableProps}
|
||||
ref={droppableProvided.innerRef}
|
||||
className={`draggable-list ${droppableSnapshot.isDraggingOver ? 'list-group bg-primary' : 'list-group'} border border-dark rounded-1`}
|
||||
className={`draggable-list ${
|
||||
droppableSnapshot.isDraggingOver
|
||||
? 'list-group bg-primary'
|
||||
: 'list-group'
|
||||
} border border-dark rounded-1`}
|
||||
>
|
||||
{listData[droppableId].map((item, l) => (
|
||||
<Draggable
|
||||
key={item.id}
|
||||
draggableId={item.id}
|
||||
index={l}
|
||||
>
|
||||
<Draggable key={item.id} draggableId={item.id} index={l}>
|
||||
{(draggableProvided, draggableSnapshot) => (
|
||||
<div
|
||||
ref={draggableProvided.innerRef}
|
||||
{...draggableProvided.draggableProps}
|
||||
{...draggableProvided.dragHandleProps}
|
||||
className={draggableSnapshot.isDragging ? 'list-group-item active' : 'list-group-item'}
|
||||
className={
|
||||
draggableSnapshot.isDragging
|
||||
? 'list-group-item active'
|
||||
: 'list-group-item'
|
||||
}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
@@ -105,7 +124,7 @@ const DraggableListGroup = ({ groupName, titles, dataSources, onChange, ...props
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import './FormCheck.scss'
|
||||
import './FormCheck.scss';
|
||||
|
||||
const FormCheck = ({ label, error, groupClassName, ...props }) => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
const FormControl = ({ onClick, label, error, groupClassName, hidden, labelClassName, ...props }) => {
|
||||
const FormControl = ({
|
||||
onClick,
|
||||
label,
|
||||
error,
|
||||
groupClassName,
|
||||
hidden,
|
||||
labelClassName,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Form.Group className={groupClassName} onClick={onClick} hidden={hidden}>
|
||||
<Form.Label className={labelClassName}>{label}</Form.Label>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FormSelect from './FormSelect';
|
||||
import { KEY_CODES } from "../Data/Keyboard";
|
||||
import { KEY_CODES } from '../Data/Keyboard';
|
||||
import { BUTTONS } from '../Data/Buttons';
|
||||
import boards from '../Data/Boards.json';
|
||||
|
||||
const KeyboardMapper = ({ buttonLabels, handleKeyChange, validated, getKeyMappingForButton, ...props }) => {
|
||||
const KeyboardMapper = ({
|
||||
buttonLabels,
|
||||
handleKeyChange,
|
||||
validated,
|
||||
getKeyMappingForButton,
|
||||
...props
|
||||
}) => {
|
||||
const { buttonLabelType, swapTpShareLabels } = buttonLabels;
|
||||
|
||||
const { t } = useTranslation('Components', { keyPrefix: 'keyboard-mapper' });
|
||||
@@ -14,36 +20,64 @@ const KeyboardMapper = ({ buttonLabels, handleKeyChange, validated, getKeyMappin
|
||||
<table className="table table-sm pin-mapping-table" {...props}>
|
||||
<thead className="table">
|
||||
<tr>
|
||||
<th className="table-header-button-label">{BUTTONS[buttonLabelType].label}</th>
|
||||
<th className="table-header-button-label">
|
||||
{BUTTONS[buttonLabelType].label}
|
||||
</th>
|
||||
<th>{t('key-header')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(BUTTONS[buttonLabelType])?.filter(p => p !== 'label' && p !== 'value').map((button, i) => {
|
||||
let label = BUTTONS[buttonLabelType][button];
|
||||
if (button === "S1" && swapTpShareLabels && buttonLabelType === "ps4") {
|
||||
label = BUTTONS[buttonLabelType]["A2"];
|
||||
}
|
||||
if (button === "A2" && swapTpShareLabels && buttonLabelType === "ps4") {
|
||||
label = BUTTONS[buttonLabelType]["S1"];
|
||||
}
|
||||
const keyMapping = getKeyMappingForButton(button);
|
||||
return <tr key={`button-map-${i}`} className={validated && !!keyMapping.error ? "table-danger" : ""}>
|
||||
<td>{label}</td>
|
||||
<td>
|
||||
<FormSelect
|
||||
type="number"
|
||||
className="form-select-sm sm-3"
|
||||
value={keyMapping.key}
|
||||
isInvalid={!!keyMapping.error}
|
||||
error={keyMapping.error}
|
||||
onChange={(e) => handleKeyChange(e.target.value ? parseInt(e.target.value) : '', button)}
|
||||
{Object.keys(BUTTONS[buttonLabelType])
|
||||
?.filter((p) => p !== 'label' && p !== 'value')
|
||||
.map((button, i) => {
|
||||
let label = BUTTONS[buttonLabelType][button];
|
||||
if (
|
||||
button === 'S1' &&
|
||||
swapTpShareLabels &&
|
||||
buttonLabelType === 'ps4'
|
||||
) {
|
||||
label = BUTTONS[buttonLabelType]['A2'];
|
||||
}
|
||||
if (
|
||||
button === 'A2' &&
|
||||
swapTpShareLabels &&
|
||||
buttonLabelType === 'ps4'
|
||||
) {
|
||||
label = BUTTONS[buttonLabelType]['S1'];
|
||||
}
|
||||
const keyMapping = getKeyMappingForButton(button);
|
||||
return (
|
||||
<tr
|
||||
key={`button-map-${i}`}
|
||||
className={
|
||||
validated && !!keyMapping.error ? 'table-danger' : ''
|
||||
}
|
||||
>
|
||||
{KEY_CODES.map((o, i) => <option key={`button-key-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</FormSelect>
|
||||
</td>
|
||||
</tr>
|
||||
})}
|
||||
<td>{label}</td>
|
||||
<td>
|
||||
<FormSelect
|
||||
type="number"
|
||||
className="form-select-sm sm-3"
|
||||
value={keyMapping.key}
|
||||
isInvalid={!!keyMapping.error}
|
||||
error={keyMapping.error}
|
||||
onChange={(e) =>
|
||||
handleKeyChange(
|
||||
e.target.value ? parseInt(e.target.value) : '',
|
||||
button,
|
||||
)
|
||||
}
|
||||
>
|
||||
{KEY_CODES.map((o, i) => (
|
||||
<option key={`button-key-option-${i}`} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
@@ -56,18 +90,29 @@ export const validateMappings = (mappings, t) => {
|
||||
mappings[prop].error = null;
|
||||
|
||||
for (let otherProp of props) {
|
||||
if (prop === otherProp)
|
||||
continue;
|
||||
if (prop === otherProp) continue;
|
||||
|
||||
const key = KEY_CODES.find(({ _, value }) => mappings[prop].key === value).label;
|
||||
const key = KEY_CODES.find(
|
||||
({ _, value }) => mappings[prop].key === value,
|
||||
).label;
|
||||
|
||||
if (mappings[prop].key !== 0x00 && mappings[prop].key === mappings[otherProp].key) {
|
||||
mappings[prop].error = t('Components:keyboard-mapper.error-conflict', { key });
|
||||
} else if ((boards[import.meta.env.VITE_GP2040_BOARD].invalidKeys || []).filter(p => p === mappings[prop].key).length > 0) {
|
||||
mappings[prop].error = t('Components:keyboard-mapper.error-invalid', { key });
|
||||
if (
|
||||
mappings[prop].key !== 0x00 &&
|
||||
mappings[prop].key === mappings[otherProp].key
|
||||
) {
|
||||
mappings[prop].error = t('Components:keyboard-mapper.error-conflict', {
|
||||
key,
|
||||
});
|
||||
} else if (
|
||||
(boards[import.meta.env.VITE_GP2040_BOARD].invalidKeys || []).filter(
|
||||
(p) => p === mappings[prop].key,
|
||||
).length > 0
|
||||
) {
|
||||
mappings[prop].error = t('Components:keyboard-mapper.error-invalid', {
|
||||
key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return mappings;
|
||||
|
||||
@@ -2,9 +2,9 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { Dropdown } from 'react-bootstrap';
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import GlobeIcon from "../Icons/Globe";
|
||||
import GbFlag from "../Icons/Flags/Gb";
|
||||
import UsFlag from "../Icons/Flags/Us";
|
||||
import GlobeIcon from '../Icons/Globe';
|
||||
import GbFlag from '../Icons/Flags/Gb';
|
||||
import UsFlag from '../Icons/Flags/Us';
|
||||
|
||||
const dropdownOptions = [
|
||||
{ code: 'en', icon: UsFlag },
|
||||
@@ -16,7 +16,7 @@ const LanguageSelector = () => {
|
||||
const { i18n, t } = useTranslation('Components');
|
||||
|
||||
useEffect(() => {
|
||||
if (!dropdownOptions.some(o => o.code === i18n.language)) {
|
||||
if (!dropdownOptions.some((o) => o.code === i18n.language)) {
|
||||
setSavedLanguage(dropdownOptions[0].code);
|
||||
} else {
|
||||
setSavedLanguage(i18n.language);
|
||||
@@ -34,7 +34,7 @@ const LanguageSelector = () => {
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle variant="secondary" style={{ marginRight: "7px" }}>
|
||||
<Dropdown.Toggle variant="secondary" style={{ marginRight: '7px' }}>
|
||||
<GlobeIcon />
|
||||
</Dropdown.Toggle>
|
||||
|
||||
@@ -42,7 +42,9 @@ const LanguageSelector = () => {
|
||||
{dropdownOptions.map((option) => (
|
||||
<Dropdown.Item
|
||||
key={option.code}
|
||||
className={`dropdown-item ${savedLanguage === option.code ? 'active' : ''}`}
|
||||
className={`dropdown-item ${
|
||||
savedLanguage === option.code ? 'active' : ''
|
||||
}`}
|
||||
onClick={() => setLanguageAndState(option.code)}
|
||||
>
|
||||
<option.icon /> {t(`language-selector.${option.code}`)}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Nav, NavDropdown, Navbar, Button, Modal, Dropdown } from 'react-bootstrap';
|
||||
import { NavLink } from "react-router-dom";
|
||||
import {
|
||||
Nav,
|
||||
NavDropdown,
|
||||
Navbar,
|
||||
Button,
|
||||
Modal,
|
||||
Dropdown,
|
||||
} from 'react-bootstrap';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import FormSelect from './FormSelect';
|
||||
@@ -14,7 +21,7 @@ import LanguageSelector from './LanguageSelector';
|
||||
const BOOT_MODES = {
|
||||
GAMEPAD: 0,
|
||||
WEBCONFIG: 1,
|
||||
BOOTSEL: 2
|
||||
BOOTSEL: 2,
|
||||
};
|
||||
|
||||
const Navigation = (props) => {
|
||||
@@ -24,9 +31,15 @@ const Navigation = (props) => {
|
||||
const [isRebooting, setIsRebooting] = useState(null); // null because we want the button to assume untouched state
|
||||
|
||||
const handleClose = () => setShow(false);
|
||||
const handleShow = () => { setIsRebooting(null); setShow(true); }
|
||||
const handleShow = () => {
|
||||
setIsRebooting(null);
|
||||
setShow(true);
|
||||
};
|
||||
const handleReboot = async (bootMode) => {
|
||||
if (isRebooting === false) { setShow(false); return; }
|
||||
if (isRebooting === false) {
|
||||
setShow(false);
|
||||
return;
|
||||
}
|
||||
setIsRebooting(bootMode);
|
||||
await WebApi.reboot(bootMode);
|
||||
setIsRebooting(-1);
|
||||
@@ -40,27 +53,61 @@ const Navigation = (props) => {
|
||||
const { t } = useTranslation('');
|
||||
|
||||
return (
|
||||
<Navbar collapseOnSelect bg="primary" variant="dark" expand="md" fixed="top">
|
||||
<Navbar
|
||||
collapseOnSelect
|
||||
bg="primary"
|
||||
variant="dark"
|
||||
expand="md"
|
||||
fixed="top"
|
||||
>
|
||||
<Navbar.Brand href="/">
|
||||
<img src="images/logo.png" className="title-logo" alt="logo" />{' '}{t('Common:brand-text')}
|
||||
<img src="images/logo.png" className="title-logo" alt="logo" />{' '}
|
||||
{t('Common:brand-text')}
|
||||
</Navbar.Brand>
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="me-auto">
|
||||
<Nav.Link as={NavLink} exact="true" to="/">{t('Navigation:home-label')}</Nav.Link>
|
||||
<Nav.Link as={NavLink} exact="true" to="/settings">{t('Navigation:settings-label')}</Nav.Link>
|
||||
<Nav.Link as={NavLink} exact="true" to="/">
|
||||
{t('Navigation:home-label')}
|
||||
</Nav.Link>
|
||||
<Nav.Link as={NavLink} exact="true" to="/settings">
|
||||
{t('Navigation:settings-label')}
|
||||
</Nav.Link>
|
||||
<NavDropdown title={t('Navigation:config-label')}>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/pin-mapping">{t('Navigation:pin-mapping-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/keyboard-mapping">{t('Navigation:keyboard-mapping-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/profile-settings">{t('Navigation:profile-settings-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/led-config">{t('Navigation:led-config-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/custom-theme">{t('Navigation:custom-theme-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/display-config">{t('Navigation:display-config-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/add-ons">{t('Navigation:add-ons-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/backup">{t('Navigation:backup-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/pin-mapping">
|
||||
{t('Navigation:pin-mapping-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/keyboard-mapping">
|
||||
{t('Navigation:keyboard-mapping-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/profile-settings">
|
||||
{t('Navigation:profile-settings-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/led-config">
|
||||
{t('Navigation:led-config-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/custom-theme">
|
||||
{t('Navigation:custom-theme-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/display-config">
|
||||
{t('Navigation:display-config-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/add-ons">
|
||||
{t('Navigation:add-ons-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item as={NavLink} exact="true" to="/backup">
|
||||
{t('Navigation:backup-label')}
|
||||
</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
<NavDropdown title="Links">
|
||||
<NavDropdown.Item href="https://gp2040-ce.info/" target="_blank">{t('Navigation:docs-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item href="https://github.com/OpenStickCommunity/GP2040-CE" target="_blank">{t('Navigation:github-label')}</NavDropdown.Item>
|
||||
<NavDropdown.Item href="https://gp2040-ce.info/" target="_blank">
|
||||
{t('Navigation:docs-label')}
|
||||
</NavDropdown.Item>
|
||||
<NavDropdown.Item
|
||||
href="https://github.com/OpenStickCommunity/GP2040-CE"
|
||||
target="_blank"
|
||||
>
|
||||
{t('Navigation:github-label')}
|
||||
</NavDropdown.Item>
|
||||
</NavDropdown>
|
||||
|
||||
<Dropdown>
|
||||
@@ -69,26 +116,37 @@ const Navigation = (props) => {
|
||||
</Dropdown.Toggle>
|
||||
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href="/reset-settings">{t('Navigation:resetSettings-label')}</Dropdown.Item>
|
||||
<Dropdown.Item href="/reset-settings">
|
||||
{t('Navigation:resetSettings-label')}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Nav>
|
||||
<Nav>
|
||||
<LanguageSelector />
|
||||
<ColorScheme />
|
||||
<Button style={{ marginRight: "7px" }} variant="success" onClick={handleShow}>
|
||||
<Button
|
||||
style={{ marginRight: '7px' }}
|
||||
variant="success"
|
||||
onClick={handleShow}
|
||||
>
|
||||
{t('Navigation:reboot-label')}
|
||||
</Button>
|
||||
<div style={{ marginTop: "4px", marginRight: "10px" }}>
|
||||
<div style={{ marginTop: '4px', marginRight: '10px' }}>
|
||||
<FormSelect
|
||||
name="buttonLabels"
|
||||
className="form-select-sm"
|
||||
value={buttonLabels.buttonLabelType}
|
||||
onChange={updateButtonLabels}
|
||||
>
|
||||
{Object.keys(BUTTONS).map((b, i) =>
|
||||
<option key={`button-label-option-${i}`} value={BUTTONS[b].value}>{BUTTONS[b].label}</option>
|
||||
)}
|
||||
{Object.keys(BUTTONS).map((b, i) => (
|
||||
<option
|
||||
key={`button-label-option-${i}`}
|
||||
value={BUTTONS[b].value}
|
||||
>
|
||||
{BUTTONS[b].label}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
</div>
|
||||
@@ -99,17 +157,41 @@ const Navigation = (props) => {
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('Navigation:reboot-modal-label')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{isRebooting === -1 ? t('Navigation:reboot-modal-success')
|
||||
: t('Navigation:reboot-modal-body')}</Modal.Body>
|
||||
<Modal.Body>
|
||||
{isRebooting === -1
|
||||
? t('Navigation:reboot-modal-success')
|
||||
: t('Navigation:reboot-modal-body')}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => handleReboot(BOOT_MODES.BOOTSEL)}>
|
||||
{isRebooting !== BOOT_MODES.BOOTSEL ? t('Navigation:reboot-modal-button-bootsel-label') : (isRebooting ? t('Navigation:reboot-modal-button-progress-label') : t('Navigation:reboot-modal-button-success-label'))}
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleReboot(BOOT_MODES.BOOTSEL)}
|
||||
>
|
||||
{isRebooting !== BOOT_MODES.BOOTSEL
|
||||
? t('Navigation:reboot-modal-button-bootsel-label')
|
||||
: isRebooting
|
||||
? t('Navigation:reboot-modal-button-progress-label')
|
||||
: t('Navigation:reboot-modal-button-success-label')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => handleReboot(BOOT_MODES.WEBCONFIG)}>
|
||||
{isRebooting !== BOOT_MODES.WEBCONFIG ? t('Navigation:reboot-modal-button-web-config-label') : (isRebooting ? t('Navigation:reboot-modal-button-progress-label') : t('Navigation:reboot-modal-button-success-label'))}
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => handleReboot(BOOT_MODES.WEBCONFIG)}
|
||||
>
|
||||
{isRebooting !== BOOT_MODES.WEBCONFIG
|
||||
? t('Navigation:reboot-modal-button-web-config-label')
|
||||
: isRebooting
|
||||
? t('Navigation:reboot-modal-button-progress-label')
|
||||
: t('Navigation:reboot-modal-button-success-label')}
|
||||
</Button>
|
||||
<Button variant="success" onClick={() => handleReboot(BOOT_MODES.GAMEPAD)}>
|
||||
{isRebooting !== BOOT_MODES.GAMEPAD ? t('Navigation:reboot-modal-button-controller-label') : (isRebooting ? t('Navigation:reboot-modal-button-progress-label') : t('Navigation:reboot-modal-button-success-label'))}
|
||||
<Button
|
||||
variant="success"
|
||||
onClick={() => handleReboot(BOOT_MODES.GAMEPAD)}
|
||||
>
|
||||
{isRebooting !== BOOT_MODES.GAMEPAD
|
||||
? t('Navigation:reboot-modal-button-controller-label')
|
||||
: isRebooting
|
||||
? t('Navigation:reboot-modal-button-progress-label')
|
||||
: t('Navigation:reboot-modal-button-success-label')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
margin: -5px 8px 0 12px;
|
||||
}
|
||||
|
||||
.nav-link, .nav-item * {
|
||||
.nav-link,
|
||||
.nav-item * {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@@ -3,28 +3,29 @@ import { useTranslation } from 'react-i18next';
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import './Section.scss';
|
||||
|
||||
|
||||
const Section = ({ children, title, ...props }) => {
|
||||
const { loading } = useContext(AppContext);
|
||||
const { t } = useTranslation('');
|
||||
|
||||
return (
|
||||
<div className={`card ${props.className}`}>
|
||||
<div className={`card-header ${props.titleClassName}`}>
|
||||
<strong>{title}</strong>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{loading ? (
|
||||
<div className="d-flex justify-content-center align-items-center loading">
|
||||
<div className="spinner-border" role="status">
|
||||
<span className="visually-hidden">{t('Common:loading-text')}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</div>
|
||||
<div className={`card ${props.className}`}>
|
||||
<div className={`card-header ${props.titleClassName}`}>
|
||||
<strong>{title}</strong>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{loading ? (
|
||||
<div className="d-flex justify-content-center align-items-center loading">
|
||||
<div className="spinner-border" role="status">
|
||||
<span className="visually-hidden">
|
||||
{t('Common:loading-text')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,74 +7,111 @@ export const AppContext = createContext(null);
|
||||
|
||||
let checkPins = null;
|
||||
|
||||
yup.addMethod(yup.string, 'validateColor', function() {
|
||||
return this.test('', 'Valid hex color required', (value) => value?.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i));
|
||||
yup.addMethod(yup.string, 'validateColor', function () {
|
||||
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(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,
|
||||
'validateSelectionWhenValue',
|
||||
function (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(name) {
|
||||
yup.addMethod(yup.NumberSchema, 'validateNumberWhenValue', function (name) {
|
||||
return this.when(name, {
|
||||
is: value => !isNaN(parseInt(value)),
|
||||
is: (value) => !isNaN(parseInt(value)),
|
||||
then: () => this.required(),
|
||||
otherwise: () => yup.mixed().notRequired().strip()
|
||||
})
|
||||
});
|
||||
|
||||
yup.addMethod(yup.NumberSchema, 'validateMinWhenEqualTo', function(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(name, min, max) {
|
||||
return this.when(name, {
|
||||
is: value => !isNaN(parseInt(value)),
|
||||
then: () => this.required().min(min).max(max),
|
||||
otherwise: () => yup.mixed().notRequired().strip()
|
||||
otherwise: () => yup.mixed().notRequired().strip(),
|
||||
});
|
||||
});
|
||||
|
||||
yup.addMethod(yup.NumberSchema, 'validatePinWhenEqualTo', function(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,
|
||||
'validateMinWhenEqualTo',
|
||||
function (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, 'validatePinWhenValue', function(name) {
|
||||
yup.addMethod(
|
||||
yup.NumberSchema,
|
||||
'validateRangeWhenValue',
|
||||
function (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 (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 (name) {
|
||||
return this.checkUsedPins();
|
||||
});
|
||||
|
||||
yup.addMethod(yup.NumberSchema, 'checkUsedPins', function() {
|
||||
return this.test('', '${originalValue} is unavailable/already assigned!', (value) => checkPins(value));
|
||||
yup.addMethod(yup.NumberSchema, 'checkUsedPins', function () {
|
||||
return this.test(
|
||||
'',
|
||||
'${originalValue} is unavailable/already assigned!',
|
||||
(value) => checkPins(value),
|
||||
);
|
||||
});
|
||||
|
||||
const parseBoolean = (bool) => String(bool).toLowerCase() === "true";
|
||||
const parseBoolean = (bool) => String(bool).toLowerCase() === 'true';
|
||||
|
||||
export const AppContextProvider = ({ children, ...props }) => {
|
||||
const localValue = localStorage.getItem('buttonLabelType') || 'gp2040';
|
||||
const localValue2 = parseBoolean(localStorage.getItem('swapTpShareLabels')) || false;
|
||||
const [buttonLabels, _setButtonLabels] = useState({ swapTpShareLabels: localValue2, buttonLabelType: localValue });
|
||||
const setButtonLabels = ({ buttonLabelType : newType, swapTpShareLabels: newSwap }) => {
|
||||
console.log('buttonLabelType is', newType)
|
||||
const localValue2 =
|
||||
parseBoolean(localStorage.getItem('swapTpShareLabels')) || false;
|
||||
const [buttonLabels, _setButtonLabels] = useState({
|
||||
swapTpShareLabels: localValue2,
|
||||
buttonLabelType: localValue,
|
||||
});
|
||||
const setButtonLabels = ({
|
||||
buttonLabelType: newType,
|
||||
swapTpShareLabels: newSwap,
|
||||
}) => {
|
||||
console.log('buttonLabelType is', newType);
|
||||
newType && localStorage.setItem('buttonLabelType', newType);
|
||||
newSwap !== undefined && localStorage.setItem('swapTpShareLabels', parseBoolean(newSwap));
|
||||
_setButtonLabels(({ buttonLabelType, swapTpShareLabels }) =>
|
||||
({ buttonLabelType: newType || buttonLabelType,
|
||||
swapTpShareLabels: parseBoolean((newSwap !== undefined) ? newSwap : swapTpShareLabels) }));
|
||||
newSwap !== undefined &&
|
||||
localStorage.setItem('swapTpShareLabels', parseBoolean(newSwap));
|
||||
_setButtonLabels(({ buttonLabelType, swapTpShareLabels }) => ({
|
||||
buttonLabelType: newType || buttonLabelType,
|
||||
swapTpShareLabels: parseBoolean(
|
||||
newSwap !== undefined ? newSwap : swapTpShareLabels,
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const [savedColors, _setSavedColors] = useState(localStorage.getItem('savedColors') ? localStorage.getItem('savedColors').split(',') : []);
|
||||
const [savedColors, _setSavedColors] = useState(
|
||||
localStorage.getItem('savedColors')
|
||||
? localStorage.getItem('savedColors').split(',')
|
||||
: [],
|
||||
);
|
||||
const setSavedColors = (savedColors) => {
|
||||
localStorage.setItem('savedColors', savedColors);
|
||||
_setSavedColors(savedColors);
|
||||
@@ -82,22 +119,30 @@ export const AppContextProvider = ({ children, ...props }) => {
|
||||
|
||||
const updateButtonLabels = (e) => {
|
||||
const { key, newValue } = e;
|
||||
if (key === "swapTpShareLabels") {
|
||||
_setButtonLabels(({ buttonLabelType }) => ({ buttonLabelType, swapTpShareLabels: parseBoolean(newValue) }));
|
||||
if (key === 'swapTpShareLabels') {
|
||||
_setButtonLabels(({ buttonLabelType }) => ({
|
||||
buttonLabelType,
|
||||
swapTpShareLabels: parseBoolean(newValue),
|
||||
}));
|
||||
}
|
||||
if (key === "buttonLabelType") {
|
||||
_setButtonLabels(({ swapTpShareLabels }) => ({ buttonLabelType: newValue, swapTpShareLabels: parseBoolean(swapTpShareLabels) }));
|
||||
if (key === 'buttonLabelType') {
|
||||
_setButtonLabels(({ swapTpShareLabels }) => ({
|
||||
buttonLabelType: newValue,
|
||||
swapTpShareLabels: parseBoolean(swapTpShareLabels),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
_setButtonLabels({ buttonLabelType: buttonLabels.buttonLabelType,
|
||||
swapTpShareLabels: parseBoolean(buttonLabels.swapTpShareLabels) });
|
||||
window.addEventListener("storage", updateButtonLabels);
|
||||
_setButtonLabels({
|
||||
buttonLabelType: buttonLabels.buttonLabelType,
|
||||
swapTpShareLabels: parseBoolean(buttonLabels.swapTpShareLabels),
|
||||
});
|
||||
window.addEventListener('storage', updateButtonLabels);
|
||||
return () => {
|
||||
window.removeEventListener("storage", updateButtonLabels);
|
||||
window.removeEventListener('storage', updateButtonLabels);
|
||||
};
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
const [gradientNormalColor1, _setGradientNormalColor1] = useState('#00ffff');
|
||||
const setGradientNormalColor1 = (gradientNormalColor1) => {
|
||||
@@ -111,13 +156,15 @@ export const AppContextProvider = ({ children, ...props }) => {
|
||||
_setGradientNormalColor1(gradientNormalColor2);
|
||||
};
|
||||
|
||||
const [gradientPressedColor1, _setGradientPressedColor1] = useState('#ff00ff');
|
||||
const [gradientPressedColor1, _setGradientPressedColor1] =
|
||||
useState('#ff00ff');
|
||||
const setGradientPressedColor1 = (gradientPressedColor1) => {
|
||||
localStorage.setItem('gradientPressedColor1', gradientPressedColor1);
|
||||
_setGradientPressedColor1(gradientPressedColor1);
|
||||
};
|
||||
|
||||
const [gradientPressedColor2, _setGradientPressedColor2] = useState('#00ffff');
|
||||
const [gradientPressedColor2, _setGradientPressedColor2] =
|
||||
useState('#00ffff');
|
||||
const setGradientPressedColor2 = (gradientPressedColor2) => {
|
||||
localStorage.setItem('gradientPressedColor2', gradientPressedColor2);
|
||||
_setGradientPressedColor1(gradientPressedColor2);
|
||||
@@ -139,22 +186,27 @@ export const AppContextProvider = ({ children, ...props }) => {
|
||||
useEffect(() => {
|
||||
checkPins = (value) => {
|
||||
const hasValue = value > -1;
|
||||
const isValid = value === undefined
|
||||
|| value === -1
|
||||
|| (hasValue && value < 30 && (usedPins || []).indexOf(value) === -1);
|
||||
const isValid =
|
||||
value === undefined ||
|
||||
value === -1 ||
|
||||
(hasValue && value < 30 && (usedPins || []).indexOf(value) === -1);
|
||||
return isValid;
|
||||
};
|
||||
}, [usedPins, setUsedPins]);
|
||||
|
||||
console.log('usedPins:', usedPins);
|
||||
|
||||
const [savedColorScheme, _setSavedColorScheme] = useState(localStorage.getItem('savedColorScheme') || 'auto');
|
||||
const [savedColorScheme, _setSavedColorScheme] = useState(
|
||||
localStorage.getItem('savedColorScheme') || 'auto',
|
||||
);
|
||||
const setSavedColorScheme = (savedColorScheme) => {
|
||||
localStorage.setItem('savedColorScheme', savedColorScheme);
|
||||
_setSavedColorScheme(savedColorScheme);
|
||||
};
|
||||
|
||||
const [savedLanguage, _setSavedLanguage] = useState(localStorage.getItem('i18next') || 'en-GB');
|
||||
const [savedLanguage, _setSavedLanguage] = useState(
|
||||
localStorage.getItem('i18next') || 'en-GB',
|
||||
);
|
||||
const setSavedLanguage = (savedLanguage) => {
|
||||
localStorage.setItem('i18next', savedLanguage);
|
||||
_setSavedLanguage(savedLanguage);
|
||||
@@ -186,7 +238,7 @@ export const AppContextProvider = ({ children, ...props }) => {
|
||||
savedLanguage,
|
||||
setSavedLanguage,
|
||||
loading,
|
||||
setLoading
|
||||
setLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,168 +1,181 @@
|
||||
export const BUTTONS = {
|
||||
gp2040: {
|
||||
label: "GP2040",
|
||||
value: "gp2040",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "B1",
|
||||
B2: "B2",
|
||||
B3: "B3",
|
||||
B4: "B4",
|
||||
L1: "L1",
|
||||
R1: "R1",
|
||||
L2: "L2",
|
||||
R2: "R2",
|
||||
S1: "S1",
|
||||
S2: "S2",
|
||||
L3: "L3",
|
||||
R3: "R3",
|
||||
A1: "A1",
|
||||
A2: "A2",
|
||||
Fn: "Function"
|
||||
label: 'GP2040',
|
||||
value: 'gp2040',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: 'B1',
|
||||
B2: 'B2',
|
||||
B3: 'B3',
|
||||
B4: 'B4',
|
||||
L1: 'L1',
|
||||
R1: 'R1',
|
||||
L2: 'L2',
|
||||
R2: 'R2',
|
||||
S1: 'S1',
|
||||
S2: 'S2',
|
||||
L3: 'L3',
|
||||
R3: 'R3',
|
||||
A1: 'A1',
|
||||
A2: 'A2',
|
||||
Fn: 'Function',
|
||||
},
|
||||
arcade: {
|
||||
label: "Arcade",
|
||||
value: "arcade",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "K1",
|
||||
B2: "K2",
|
||||
B3: "P1",
|
||||
B4: "P2",
|
||||
L1: "P4",
|
||||
R1: "P3",
|
||||
L2: "K4",
|
||||
R2: "K3",
|
||||
S1: "Select",
|
||||
S2: "Start",
|
||||
L3: "L3",
|
||||
R3: "R3",
|
||||
A1: "Home",
|
||||
A2: "-",
|
||||
Fn: "Function"
|
||||
label: 'Arcade',
|
||||
value: 'arcade',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: 'K1',
|
||||
B2: 'K2',
|
||||
B3: 'P1',
|
||||
B4: 'P2',
|
||||
L1: 'P4',
|
||||
R1: 'P3',
|
||||
L2: 'K4',
|
||||
R2: 'K3',
|
||||
S1: 'Select',
|
||||
S2: 'Start',
|
||||
L3: 'L3',
|
||||
R3: 'R3',
|
||||
A1: 'Home',
|
||||
A2: '-',
|
||||
Fn: 'Function',
|
||||
},
|
||||
xinput: {
|
||||
label: "XInput",
|
||||
value: "xinput",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "A",
|
||||
B2: "B",
|
||||
B3: "X",
|
||||
B4: "Y",
|
||||
L1: "LB",
|
||||
R1: "RB",
|
||||
L2: "LT",
|
||||
R2: "RT",
|
||||
S1: "Back",
|
||||
S2: "Start",
|
||||
L3: "LS",
|
||||
R3: "RS",
|
||||
A1: "Guide",
|
||||
A2: "-",
|
||||
Fn: "Function"
|
||||
label: 'XInput',
|
||||
value: 'xinput',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: 'A',
|
||||
B2: 'B',
|
||||
B3: 'X',
|
||||
B4: 'Y',
|
||||
L1: 'LB',
|
||||
R1: 'RB',
|
||||
L2: 'LT',
|
||||
R2: 'RT',
|
||||
S1: 'Back',
|
||||
S2: 'Start',
|
||||
L3: 'LS',
|
||||
R3: 'RS',
|
||||
A1: 'Guide',
|
||||
A2: '-',
|
||||
Fn: 'Function',
|
||||
},
|
||||
switch: {
|
||||
label: "Nintendo Switch",
|
||||
value: "switch",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "B",
|
||||
B2: "A",
|
||||
B3: "Y",
|
||||
B4: "X",
|
||||
L1: "L",
|
||||
R1: "R",
|
||||
L2: "ZL",
|
||||
R2: "ZR",
|
||||
S1: "Minus",
|
||||
S2: "Plus",
|
||||
L3: "LS",
|
||||
R3: "RS",
|
||||
A1: "Home",
|
||||
A2: "Capture",
|
||||
Fn: "Function"
|
||||
label: 'Nintendo Switch',
|
||||
value: 'switch',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: 'B',
|
||||
B2: 'A',
|
||||
B3: 'Y',
|
||||
B4: 'X',
|
||||
L1: 'L',
|
||||
R1: 'R',
|
||||
L2: 'ZL',
|
||||
R2: 'ZR',
|
||||
S1: 'Minus',
|
||||
S2: 'Plus',
|
||||
L3: 'LS',
|
||||
R3: 'RS',
|
||||
A1: 'Home',
|
||||
A2: 'Capture',
|
||||
Fn: 'Function',
|
||||
},
|
||||
ps3: {
|
||||
label: "PS3",
|
||||
value: "ps3",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "Cross",
|
||||
B2: "Circle",
|
||||
B3: "Square",
|
||||
B4: "Triangle",
|
||||
L1: "L1",
|
||||
R1: "R1",
|
||||
L2: "L2",
|
||||
R2: "R2",
|
||||
S1: "Select",
|
||||
S2: "Start",
|
||||
L3: "L3",
|
||||
R3: "R3",
|
||||
A1: "PS",
|
||||
A2: "-",
|
||||
Fn: "Function"
|
||||
label: 'PS3',
|
||||
value: 'ps3',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: 'Cross',
|
||||
B2: 'Circle',
|
||||
B3: 'Square',
|
||||
B4: 'Triangle',
|
||||
L1: 'L1',
|
||||
R1: 'R1',
|
||||
L2: 'L2',
|
||||
R2: 'R2',
|
||||
S1: 'Select',
|
||||
S2: 'Start',
|
||||
L3: 'L3',
|
||||
R3: 'R3',
|
||||
A1: 'PS',
|
||||
A2: '-',
|
||||
Fn: 'Function',
|
||||
},
|
||||
ps4: {
|
||||
label: "PS4",
|
||||
value: "ps4",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "Cross",
|
||||
B2: "Circle",
|
||||
B3: "Square",
|
||||
B4: "Triangle",
|
||||
L1: "L1",
|
||||
R1: "R1",
|
||||
L2: "L2",
|
||||
R2: "R2",
|
||||
S1: "Share",
|
||||
S2: "Options",
|
||||
L3: "L3",
|
||||
R3: "R3",
|
||||
A1: "PS",
|
||||
A2: "Touchpad"
|
||||
label: 'PS4',
|
||||
value: 'ps4',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: 'Cross',
|
||||
B2: 'Circle',
|
||||
B3: 'Square',
|
||||
B4: 'Triangle',
|
||||
L1: 'L1',
|
||||
R1: 'R1',
|
||||
L2: 'L2',
|
||||
R2: 'R2',
|
||||
S1: 'Share',
|
||||
S2: 'Options',
|
||||
L3: 'L3',
|
||||
R3: 'R3',
|
||||
A1: 'PS',
|
||||
A2: 'Touchpad',
|
||||
},
|
||||
dinput: {
|
||||
label: "DirectInput",
|
||||
value: "dinput",
|
||||
Up: "Up",
|
||||
Down: "Down",
|
||||
Left: "Left",
|
||||
Right: "Right",
|
||||
B1: "2",
|
||||
B2: "3",
|
||||
B3: "1",
|
||||
B4: "4",
|
||||
L1: "5",
|
||||
R1: "6",
|
||||
L2: "7",
|
||||
R2: "8",
|
||||
S1: "9",
|
||||
S2: "10",
|
||||
L3: "11",
|
||||
R3: "12",
|
||||
A1: "13",
|
||||
A2: "14",
|
||||
Fn: "Function"
|
||||
}
|
||||
label: 'DirectInput',
|
||||
value: 'dinput',
|
||||
Up: 'Up',
|
||||
Down: 'Down',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
B1: '2',
|
||||
B2: '3',
|
||||
B3: '1',
|
||||
B4: '4',
|
||||
L1: '5',
|
||||
R1: '6',
|
||||
L2: '7',
|
||||
R2: '8',
|
||||
S1: '9',
|
||||
S2: '10',
|
||||
L3: '11',
|
||||
R3: '12',
|
||||
A1: '13',
|
||||
A2: '14',
|
||||
Fn: 'Function',
|
||||
},
|
||||
};
|
||||
|
||||
export const AUX_BUTTONS = [ 'S1', 'S2', 'L3', 'R3', 'A1', 'A2', 'Fn' ];
|
||||
export const MAIN_BUTTONS = [ 'Up', 'Down', 'Left', 'Right', 'B1', 'B2', 'B3', 'B4', 'L1', 'R1', 'L2', 'R2' ];
|
||||
export const AUX_BUTTONS = ['S1', 'S2', 'L3', 'R3', 'A1', 'A2', 'Fn'];
|
||||
export const MAIN_BUTTONS = [
|
||||
'Up',
|
||||
'Down',
|
||||
'Left',
|
||||
'Right',
|
||||
'B1',
|
||||
'B2',
|
||||
'B3',
|
||||
'B4',
|
||||
'L1',
|
||||
'R1',
|
||||
'L2',
|
||||
'R2',
|
||||
];
|
||||
|
||||
export const STICK_LAYOUT = [
|
||||
[null, 'Left', null],
|
||||
@@ -196,23 +209,23 @@ export const KEYBOARD_LAYOUT = [
|
||||
];
|
||||
|
||||
export const BUTTON_MASKS = [
|
||||
{ label: 'None', value: 0 },
|
||||
{ label: 'B1', value: (1 << 0) },
|
||||
{ label: 'B2', value: (1 << 1) },
|
||||
{ label: 'B3', value: (1 << 2) },
|
||||
{ label: 'B4', value: (1 << 3) },
|
||||
{ label: 'L1', value: (1 << 4) },
|
||||
{ label: 'R1', value: (1 << 5) },
|
||||
{ label: 'L2', value: (1 << 6) },
|
||||
{ label: 'R2', value: (1 << 7) },
|
||||
{ label: 'S1', value: (1 << 8) },
|
||||
{ label: 'S2', value: (1 << 9) },
|
||||
{ label: 'L3', value: (1 << 10) },
|
||||
{ label: 'R3', value: (1 << 11) },
|
||||
{ label: 'A1', value: (1 << 12) },
|
||||
{ label: 'A2', value: (1 << 13) },
|
||||
{ label: 'Up', value: (1 << 16) },
|
||||
{ label: 'Down', value: (1 << 17) },
|
||||
{ label: 'Left', value: (1 << 18) },
|
||||
{ label: 'Right', value: (1 << 19) },
|
||||
{ label: 'None', value: 0 },
|
||||
{ label: 'B1', value: 1 << 0 },
|
||||
{ label: 'B2', value: 1 << 1 },
|
||||
{ label: 'B3', value: 1 << 2 },
|
||||
{ label: 'B4', value: 1 << 3 },
|
||||
{ label: 'L1', value: 1 << 4 },
|
||||
{ label: 'R1', value: 1 << 5 },
|
||||
{ label: 'L2', value: 1 << 6 },
|
||||
{ label: 'R2', value: 1 << 7 },
|
||||
{ label: 'S1', value: 1 << 8 },
|
||||
{ label: 'S2', value: 1 << 9 },
|
||||
{ label: 'L3', value: 1 << 10 },
|
||||
{ label: 'R3', value: 1 << 11 },
|
||||
{ label: 'A1', value: 1 << 12 },
|
||||
{ label: 'A2', value: 1 << 13 },
|
||||
{ label: 'Up', value: 1 << 16 },
|
||||
{ label: 'Down', value: 1 << 17 },
|
||||
{ label: 'Left', value: 1 << 18 },
|
||||
{ label: 'Right', value: 1 << 19 },
|
||||
];
|
||||
|
||||
@@ -16,111 +16,111 @@ export const DEFAULT_KEYBOARD_MAPPING = {
|
||||
L3: 19,
|
||||
R3: 51,
|
||||
A1: 0,
|
||||
A2: 0
|
||||
A2: 0,
|
||||
};
|
||||
|
||||
export const KEY_CODES = [
|
||||
{ label: "None", value: 0x00 },
|
||||
{ label: "Alt Left", value: 0xe2 },
|
||||
{ label: "Alt Right", value: 0xe6 },
|
||||
{ label: "Arrow Down", value: 0x51 },
|
||||
{ label: "Arrow Left", value: 0x50 },
|
||||
{ label: "Arrow Right", value: 0x4f },
|
||||
{ label: "Arrow Up", value: 0x52 },
|
||||
{ label: "Backquote", value: 0x35 },
|
||||
{ label: "Backslash", value: 0x31 },
|
||||
{ label: "Backspace", value: 0x2a },
|
||||
{ label: "Bracket Left", value: 0x2f },
|
||||
{ label: "Bracket Right", value: 0x30 },
|
||||
{ label: "CapsLock", value: 0x39 },
|
||||
{ label: "Comma", value: 0x36 },
|
||||
{ label: "Control Left", value: 0xe0 },
|
||||
{ label: "Delete", value: 0x4c },
|
||||
{ label: "0", value: 0x27 },
|
||||
{ label: "1", value: 0x1e },
|
||||
{ label: "2", value: 0x1f },
|
||||
{ label: "3", value: 0x20 },
|
||||
{ label: "4", value: 0x21 },
|
||||
{ label: "5", value: 0x22 },
|
||||
{ label: "6", value: 0x23 },
|
||||
{ label: "7", value: 0x24 },
|
||||
{ label: "8", value: 0x25 },
|
||||
{ label: "9", value: 0x26 },
|
||||
{ label: "End", value: 0x4d },
|
||||
{ label: "Enter", value: 0x28 },
|
||||
{ label: "Equal", value: 0x2e },
|
||||
{ label: "Escape", value: 0x29 },
|
||||
{ label: "F1", value: 0x3a },
|
||||
{ label: "F2", value: 0x3b },
|
||||
{ label: "F3", value: 0x3c },
|
||||
{ label: "F4", value: 0x3d },
|
||||
{ label: "F5", value: 0x3e },
|
||||
{ label: "F6", value: 0x3f },
|
||||
{ label: "F7", value: 0x40 },
|
||||
{ label: "F8", value: 0x41 },
|
||||
{ label: "F9", value: 0x42 },
|
||||
{ label: "F10", value: 0x43 },
|
||||
{ label: "F11", value: 0x44 },
|
||||
{ label: "F12", value: 0x45 },
|
||||
{ label: "F13", value: 0x68 },
|
||||
{ label: "F14", value: 0x69 },
|
||||
{ label: "F15", value: 0x6a },
|
||||
{ label: "F16", value: 0x6b },
|
||||
{ label: "F17", value: 0x6c },
|
||||
{ label: "F18", value: 0x6d },
|
||||
{ label: "F19", value: 0x6e },
|
||||
{ label: "F20", value: 0x6f },
|
||||
{ label: "F21", value: 0x70 },
|
||||
{ label: "F22", value: 0x71 },
|
||||
{ label: "F23", value: 0x72 },
|
||||
{ label: "F24", value: 0x73 },
|
||||
{ label: "Home", value: 0x4a },
|
||||
{ label: "Intl Backslash", value: 0x31 },
|
||||
{ label: "A", value: 0x04 },
|
||||
{ label: "B", value: 0x05 },
|
||||
{ label: "C", value: 0x06 },
|
||||
{ label: "D", value: 0x07 },
|
||||
{ label: "E", value: 0x08 },
|
||||
{ label: "F", value: 0x09 },
|
||||
{ label: "G", value: 0x0a },
|
||||
{ label: "H", value: 0x0b },
|
||||
{ label: "I", value: 0x0c },
|
||||
{ label: "J", value: 0x0d },
|
||||
{ label: "K", value: 0x0e },
|
||||
{ label: "L", value: 0x0f },
|
||||
{ label: "M", value: 0x10 },
|
||||
{ label: "N", value: 0x11 },
|
||||
{ label: "O", value: 0x12 },
|
||||
{ label: "P", value: 0x13 },
|
||||
{ label: "Q", value: 0x14 },
|
||||
{ label: "R", value: 0x15 },
|
||||
{ label: "S", value: 0x16 },
|
||||
{ label: "T", value: 0x17 },
|
||||
{ label: "U", value: 0x18 },
|
||||
{ label: "V", value: 0x19 },
|
||||
{ label: "W", value: 0x1a },
|
||||
{ label: "X", value: 0x1b },
|
||||
{ label: "Y", value: 0x1c },
|
||||
{ label: "Z", value: 0x1d },
|
||||
{ label: "Meta Left", value: 0xe3 },
|
||||
{ label: "Meta Right", value: 0xe7 },
|
||||
{ label: "Minus", value: 0x2d },
|
||||
{ label: "Numpad Enter", value: 0x58 },
|
||||
{ label: "Page Down", value: 0x4e },
|
||||
{ label: "Page Up", value: 0x4b },
|
||||
{ label: "Period", value: 0x37 },
|
||||
{ label: "Quote", value: 0x34 },
|
||||
{ label: "Semicolon", value: 0x33 },
|
||||
{ label: "Shift Left", value: 0xe1 },
|
||||
{ label: "Shift Right", value: 0xe5 },
|
||||
{ label: "Slash", value: 0x38 },
|
||||
{ label: "Space", value: 0x2c },
|
||||
{ label: "Tab", value: 0x2b },
|
||||
{ label: "Next Track", value: 0xe8 }, // (Scan Next Track)
|
||||
{ label: "Prev Track", value: 0xe9 }, // (Scan Previous Track)
|
||||
{ label: "Stop", value: 0xf0 }, // (Stop)
|
||||
{ label: "Play/Pause", value: 0xf1 }, // (Play/Pause)
|
||||
{ label: "Mute", value: 0xf2 }, // (Mute)
|
||||
{ label: "Volume Up", value: 0xf3 }, // (Volume Increment)
|
||||
{ label: "Volume Down", value: 0xf4 }, // (Volume Decrement)
|
||||
];
|
||||
{ label: 'None', value: 0x00 },
|
||||
{ label: 'Alt Left', value: 0xe2 },
|
||||
{ label: 'Alt Right', value: 0xe6 },
|
||||
{ label: 'Arrow Down', value: 0x51 },
|
||||
{ label: 'Arrow Left', value: 0x50 },
|
||||
{ label: 'Arrow Right', value: 0x4f },
|
||||
{ label: 'Arrow Up', value: 0x52 },
|
||||
{ label: 'Backquote', value: 0x35 },
|
||||
{ label: 'Backslash', value: 0x31 },
|
||||
{ label: 'Backspace', value: 0x2a },
|
||||
{ label: 'Bracket Left', value: 0x2f },
|
||||
{ label: 'Bracket Right', value: 0x30 },
|
||||
{ label: 'CapsLock', value: 0x39 },
|
||||
{ label: 'Comma', value: 0x36 },
|
||||
{ label: 'Control Left', value: 0xe0 },
|
||||
{ label: 'Delete', value: 0x4c },
|
||||
{ label: '0', value: 0x27 },
|
||||
{ label: '1', value: 0x1e },
|
||||
{ label: '2', value: 0x1f },
|
||||
{ label: '3', value: 0x20 },
|
||||
{ label: '4', value: 0x21 },
|
||||
{ label: '5', value: 0x22 },
|
||||
{ label: '6', value: 0x23 },
|
||||
{ label: '7', value: 0x24 },
|
||||
{ label: '8', value: 0x25 },
|
||||
{ label: '9', value: 0x26 },
|
||||
{ label: 'End', value: 0x4d },
|
||||
{ label: 'Enter', value: 0x28 },
|
||||
{ label: 'Equal', value: 0x2e },
|
||||
{ label: 'Escape', value: 0x29 },
|
||||
{ label: 'F1', value: 0x3a },
|
||||
{ label: 'F2', value: 0x3b },
|
||||
{ label: 'F3', value: 0x3c },
|
||||
{ label: 'F4', value: 0x3d },
|
||||
{ label: 'F5', value: 0x3e },
|
||||
{ label: 'F6', value: 0x3f },
|
||||
{ label: 'F7', value: 0x40 },
|
||||
{ label: 'F8', value: 0x41 },
|
||||
{ label: 'F9', value: 0x42 },
|
||||
{ label: 'F10', value: 0x43 },
|
||||
{ label: 'F11', value: 0x44 },
|
||||
{ label: 'F12', value: 0x45 },
|
||||
{ label: 'F13', value: 0x68 },
|
||||
{ label: 'F14', value: 0x69 },
|
||||
{ label: 'F15', value: 0x6a },
|
||||
{ label: 'F16', value: 0x6b },
|
||||
{ label: 'F17', value: 0x6c },
|
||||
{ label: 'F18', value: 0x6d },
|
||||
{ label: 'F19', value: 0x6e },
|
||||
{ label: 'F20', value: 0x6f },
|
||||
{ label: 'F21', value: 0x70 },
|
||||
{ label: 'F22', value: 0x71 },
|
||||
{ label: 'F23', value: 0x72 },
|
||||
{ label: 'F24', value: 0x73 },
|
||||
{ label: 'Home', value: 0x4a },
|
||||
{ label: 'Intl Backslash', value: 0x31 },
|
||||
{ label: 'A', value: 0x04 },
|
||||
{ label: 'B', value: 0x05 },
|
||||
{ label: 'C', value: 0x06 },
|
||||
{ label: 'D', value: 0x07 },
|
||||
{ label: 'E', value: 0x08 },
|
||||
{ label: 'F', value: 0x09 },
|
||||
{ label: 'G', value: 0x0a },
|
||||
{ label: 'H', value: 0x0b },
|
||||
{ label: 'I', value: 0x0c },
|
||||
{ label: 'J', value: 0x0d },
|
||||
{ label: 'K', value: 0x0e },
|
||||
{ label: 'L', value: 0x0f },
|
||||
{ label: 'M', value: 0x10 },
|
||||
{ label: 'N', value: 0x11 },
|
||||
{ label: 'O', value: 0x12 },
|
||||
{ label: 'P', value: 0x13 },
|
||||
{ label: 'Q', value: 0x14 },
|
||||
{ label: 'R', value: 0x15 },
|
||||
{ label: 'S', value: 0x16 },
|
||||
{ label: 'T', value: 0x17 },
|
||||
{ label: 'U', value: 0x18 },
|
||||
{ label: 'V', value: 0x19 },
|
||||
{ label: 'W', value: 0x1a },
|
||||
{ label: 'X', value: 0x1b },
|
||||
{ label: 'Y', value: 0x1c },
|
||||
{ label: 'Z', value: 0x1d },
|
||||
{ label: 'Meta Left', value: 0xe3 },
|
||||
{ label: 'Meta Right', value: 0xe7 },
|
||||
{ label: 'Minus', value: 0x2d },
|
||||
{ label: 'Numpad Enter', value: 0x58 },
|
||||
{ label: 'Page Down', value: 0x4e },
|
||||
{ label: 'Page Up', value: 0x4b },
|
||||
{ label: 'Period', value: 0x37 },
|
||||
{ label: 'Quote', value: 0x34 },
|
||||
{ label: 'Semicolon', value: 0x33 },
|
||||
{ label: 'Shift Left', value: 0xe1 },
|
||||
{ label: 'Shift Right', value: 0xe5 },
|
||||
{ label: 'Slash', value: 0x38 },
|
||||
{ label: 'Space', value: 0x2c },
|
||||
{ label: 'Tab', value: 0x2b },
|
||||
{ label: 'Next Track', value: 0xe8 }, // (Scan Next Track)
|
||||
{ label: 'Prev Track', value: 0xe9 }, // (Scan Previous Track)
|
||||
{ label: 'Stop', value: 0xf0 }, // (Stop)
|
||||
{ label: 'Play/Pause', value: 0xf1 }, // (Play/Pause)
|
||||
{ label: 'Mute', value: 0xf2 }, // (Mute)
|
||||
{ label: 'Volume Up', value: 0xf3 }, // (Volume Increment)
|
||||
{ label: 'Volume Down', value: 0xf4 }, // (Volume Decrement)
|
||||
];
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
// Definitions in this file should match those in GP2040-CE/lib/AnimationStation/src/Animation.hpp
|
||||
|
||||
import { rgbArrayToHex } from "../Services/Utilities";
|
||||
import { rgbArrayToHex } from '../Services/Utilities';
|
||||
|
||||
const Black = rgbArrayToHex([0, 0, 0]);
|
||||
const White = rgbArrayToHex([255, 255, 255]);
|
||||
const Red = rgbArrayToHex([255, 0, 0]);
|
||||
const Orange = rgbArrayToHex([255, 128, 0]);
|
||||
const Yellow = rgbArrayToHex([255, 255, 0]);
|
||||
const Black = rgbArrayToHex([0, 0, 0]);
|
||||
const White = rgbArrayToHex([255, 255, 255]);
|
||||
const Red = rgbArrayToHex([255, 0, 0]);
|
||||
const Orange = rgbArrayToHex([255, 128, 0]);
|
||||
const Yellow = rgbArrayToHex([255, 255, 0]);
|
||||
const LimeGreen = rgbArrayToHex([128, 255, 0]);
|
||||
const Green = rgbArrayToHex([0, 255, 0]);
|
||||
const Seafoam = rgbArrayToHex([0, 255, 128]);
|
||||
const Aqua = rgbArrayToHex([0, 255, 255]);
|
||||
const SkyBlue = rgbArrayToHex([0, 128, 255]);
|
||||
const Blue = rgbArrayToHex([0, 0, 255]);
|
||||
const Purple = rgbArrayToHex([128, 0, 255]);
|
||||
const Pink = rgbArrayToHex([255, 0, 255]);
|
||||
const Magenta = rgbArrayToHex([255, 0, 128]);
|
||||
const Green = rgbArrayToHex([0, 255, 0]);
|
||||
const Seafoam = rgbArrayToHex([0, 255, 128]);
|
||||
const Aqua = rgbArrayToHex([0, 255, 255]);
|
||||
const SkyBlue = rgbArrayToHex([0, 128, 255]);
|
||||
const Blue = rgbArrayToHex([0, 0, 255]);
|
||||
const Purple = rgbArrayToHex([128, 0, 255]);
|
||||
const Pink = rgbArrayToHex([255, 0, 255]);
|
||||
const Magenta = rgbArrayToHex([255, 0, 128]);
|
||||
|
||||
const LEDColors = [
|
||||
{ name: Black, value: Black },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
const CircleHalf = () => (
|
||||
<svg
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
// Source: https://flagicons.lipis.dev/
|
||||
|
||||
@@ -9,11 +9,17 @@ const Gb = () => (
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<path fill="#012169" d="M0 0h640v480H0z"/>
|
||||
<path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z"/>
|
||||
<path fill="#C8102E" d="m424 281 216 159v40L369 281h55zm-184 20 6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"/>
|
||||
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z"/>
|
||||
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z"/>
|
||||
<path fill="#012169" d="M0 0h640v480H0z" />
|
||||
<path
|
||||
fill="#FFF"
|
||||
d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z"
|
||||
/>
|
||||
<path
|
||||
fill="#C8102E"
|
||||
d="m424 281 216 159v40L369 281h55zm-184 20 6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"
|
||||
/>
|
||||
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z" />
|
||||
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z" />
|
||||
</svg>
|
||||
);
|
||||
export default Gb;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
// Source: https://flagicons.lipis.dev/
|
||||
|
||||
@@ -10,12 +10,20 @@ const Us = () => (
|
||||
height="1em"
|
||||
>
|
||||
<path fill="#bd3d44" d="M0 0h640v480H0" />
|
||||
<path stroke="#fff" strokeWidth="37" d="M0 55.3h640M0 129h640M0 203h640M0 277h640M0 351h640M0 425h640" />
|
||||
<path
|
||||
stroke="#fff"
|
||||
strokeWidth="37"
|
||||
d="M0 55.3h640M0 129h640M0 203h640M0 277h640M0 351h640M0 425h640"
|
||||
/>
|
||||
<path fill="#192f5d" d="M0 0h364.8v258.5H0" />
|
||||
<marker id="a" markerHeight="30" markerWidth="30">
|
||||
<path fill="#fff" d="m14 0 9 27L0 10h28L5 27z" />
|
||||
</marker>
|
||||
<path fill="none" markerMid="url(#a)" d="m0 0 16 11h61 61 61 61 60L47 37h61 61 60 61L16 63h61 61 61 61 60L47 89h61 61 60 61L16 115h61 61 61 61 60L47 141h61 61 60 61L16 166h61 61 61 61 60L47 192h61 61 60 61L16 218h61 61 61 61 60L0 0" />
|
||||
<path
|
||||
fill="none"
|
||||
markerMid="url(#a)"
|
||||
d="m0 0 16 11h61 61 61 61 60L47 37h61 61 60 61L16 63h61 61 61 61 60L47 89h61 61 60 61L16 115h61 61 61 61 60L47 141h61 61 60 61L16 166h61 61 61 61 60L47 192h61 61 60 61L16 218h61 61 61 61 60L0 0"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default Us;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
// Source: https://icons.getbootstrap.com/icons/globe/
|
||||
|
||||
@@ -9,7 +9,7 @@ const Globe = () => (
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"/>
|
||||
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z" />
|
||||
</svg>
|
||||
);
|
||||
export default Globe;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
const MoonStars = () => (
|
||||
<svg
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
const Sun = () => (
|
||||
<svg
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
"button-set-all-to-color-label": "Set All To Colour",
|
||||
"en-GB-label": "English",
|
||||
"en-label": "American English",
|
||||
'button-set-all-to-color-label': 'Set All To Colour',
|
||||
'en-GB-label': 'English',
|
||||
'en-label': 'American English',
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
"language-selector": {
|
||||
"en-GB": "English",
|
||||
"en": "American English",
|
||||
'language-selector': {
|
||||
'en-GB': 'English',
|
||||
en: 'American English',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default {
|
||||
"list-text": "<1>Click a button to bring up the normal and pressed colour selection.</1> <1>Click on the controller background to dismiss the colour selection.</1> <1>Right-click a button to preview the button's pressed colour.</1>",
|
||||
'list-text':
|
||||
"<1>Click a button to bring up the normal and pressed colour selection.</1> <1>Click on the controller background to dismiss the colour selection.</1> <1>Right-click a button to preview the button's pressed colour.</1>",
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
"player": {
|
||||
"pled-color-label": "RGB PLED Colour",
|
||||
player: {
|
||||
'pled-color-label': 'RGB PLED Colour',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
export default {
|
||||
'header-text': 'Add-Ons Configuration',
|
||||
'sub-header-text': 'Use the form below to reconfigure add-on options in GP2040-CE.',
|
||||
'sub-header-text':
|
||||
'Use the form below to reconfigure add-on options in GP2040-CE.',
|
||||
'bootsel-header-text': 'BOOTSEL Button Configuration',
|
||||
'bootsel-sub-header-text': 'Note: OLED might become unresponsive if button is set, unset to restore.',
|
||||
'bootsel-sub-header-text':
|
||||
'Note: OLED might become unresponsive if button is set, unset to restore.',
|
||||
'bootsel-button-pin-label': 'BOOTSEL Button',
|
||||
'on-board-led-configuration-label': 'On-Board LED Configuration',
|
||||
'on-board-led-mode-label': 'LED Mode',
|
||||
'analog-header-text': 'Analog',
|
||||
'analog-warning': 'Note: Analog sticks will override gamepad Left-Stick and Right-Stick inputs when enabled',
|
||||
'analog-warning':
|
||||
'Note: Analog sticks will override gamepad Left-Stick and Right-Stick inputs when enabled',
|
||||
'analog-available-pins-text': 'Available pins: {{pins}}',
|
||||
'analog-available-pins-option-not-set': 'None',
|
||||
'analog-available-pins-option-not-set': 'None',
|
||||
'analog-adc-1-pin-x-label': 'Analog Stick 1 X Pin',
|
||||
'analog-adc-1-pin-y-label': 'Analog Stick 1 Y Pin',
|
||||
'analog-adc-1-mode-label': 'Analog Stick 1 Mode',
|
||||
@@ -69,7 +72,8 @@ export default {
|
||||
'dual-directional-input-right-pin-label': 'Dual Right Pin',
|
||||
'dual-directional-input-dpad-mode-label': 'Dual D-Pad Mode',
|
||||
'dual-directional-input-combine-mode-label': 'Combination Mode',
|
||||
'dual-directional-input-four-way-joystick-mode-label': 'Dual Directional 4-Way Joystick Mode',
|
||||
'dual-directional-input-four-way-joystick-mode-label':
|
||||
'Dual Directional 4-Way Joystick Mode',
|
||||
'tilt-header-text': 'Tilt Input',
|
||||
'tilt-1-pin-label': 'Tilt 1 Pin',
|
||||
'tilt-2-pin-label': 'Tilt 2 Pin',
|
||||
@@ -89,40 +93,49 @@ export default {
|
||||
'extra-button-pin-label': 'Extra Button Pin',
|
||||
'extra-button-map-label': 'Extra Button',
|
||||
'player-number-header-text': 'Player Number (X-INPUT ONLY)',
|
||||
'player-number-sub-header-text': 'WARNING: ONLY ENABLE THIS OPTION IF YOU ARE CONNECTING MULTIPLE GP2040-CE DEVICES WITH PLAYER NUMBER ENABLED',
|
||||
'player-number-sub-header-text':
|
||||
'WARNING: ONLY ENABLE THIS OPTION IF YOU ARE CONNECTING MULTIPLE GP2040-CE DEVICES WITH PLAYER NUMBER ENABLED',
|
||||
'player-number-label': 'Player Number',
|
||||
'socd-cleaning-mode-selection-slider-header-text': 'SOCD Cleaning Mode Selection Slider',
|
||||
'socd-cleaning-mode-selection-slider-sub-header-text': 'Note: PS4, PS3 and Nintendo Switch modes do not support setting SOCD Cleaning to Off and will default to Neutral SOCD Cleaning mode.',
|
||||
'socd-cleaning-mode-selection-slider-mode-default-label': 'SOCD Slider Mode Default',
|
||||
'socd-cleaning-mode-selection-slider-header-text':
|
||||
'SOCD Cleaning Mode Selection Slider',
|
||||
'socd-cleaning-mode-selection-slider-sub-header-text':
|
||||
'Note: PS4, PS3 and Nintendo Switch modes do not support setting SOCD Cleaning to Off and will default to Neutral SOCD Cleaning mode.',
|
||||
'socd-cleaning-mode-selection-slider-mode-default-label':
|
||||
'SOCD Slider Mode Default',
|
||||
'socd-cleaning-mode-selection-slider-mode-one-label': 'SOCD Slider Mode One',
|
||||
'socd-cleaning-mode-selection-slider-pin-one-label': 'Pin One',
|
||||
'socd-cleaning-mode-selection-slider-mode-two-label': 'SOCD Slider Mode Two',
|
||||
'socd-cleaning-mode-selection-slider-pin-two-label': 'Pin Two',
|
||||
'ps4-mode-header-text': 'PS4 Mode',
|
||||
'ps4-mode-sub-header-text': '<0>!!!! DISCLAIMER: GP2040-CE WILL NEVER SUPPLY THESE FILES !!!!</0> <1>Please upload the 3 required files and click the "Verify & Save" button to use PS4 Mode.</1>',
|
||||
'ps4-mode-sub-header-text':
|
||||
'<0>!!!! DISCLAIMER: GP2040-CE WILL NEVER SUPPLY THESE FILES !!!!</0> <1>Please upload the 3 required files and click the "Verify & Save" button to use PS4 Mode.</1>',
|
||||
'ps4-mode-private-key-label': 'Private Key (PEM)',
|
||||
'ps4-mode-serial-number-label': 'Serial Number (16 Bytes in Hex Ascii)',
|
||||
'ps4-mode-signature-label': 'Signature (256 Bytes in Binary)',
|
||||
'wii-extension-header-text': 'Wii Extension',
|
||||
'wii-extension-sub-header-text': '<0>Note: If the Display is enabled at the same time, this Addon will be disabled.</0> <1>Currently Supported Controllers</1> <0>Classic/Classic Pro: Both Analogs and D-Pad Supported. B = B1, A = B2, Y = B3, X = B4, L = L1, ZL = L2, R = R1, ZR = R2, Minus = S1, Plus = S2, Home = A1</0> <0>Nunchuck: Analog Stick Supported. C = B1, Z = B2</0> <0>Guitar Hero Guitar: Analog Stick Supported. Green = B1, Red = B2, Blue = B3, Yellow = B4, Orange = L1, Strum Up = Up, Strum Down = Down, Minus = S1, Plus = S2</0>',
|
||||
'wii-extension-sub-header-text':
|
||||
'<0>Note: If the Display is enabled at the same time, this Addon will be disabled.</0> <1>Currently Supported Controllers</1> <0>Classic/Classic Pro: Both Analogs and D-Pad Supported. B = B1, A = B2, Y = B3, X = B4, L = L1, ZL = L2, R = R1, ZR = R2, Minus = S1, Plus = S2, Home = A1</0> <0>Nunchuck: Analog Stick Supported. C = B1, Z = B2</0> <0>Guitar Hero Guitar: Analog Stick Supported. Green = B1, Red = B2, Blue = B3, Yellow = B4, Orange = L1, Strum Up = Up, Strum Down = Down, Minus = S1, Plus = S2</0>',
|
||||
'wii-extension-sda-pin-label': 'I2C SDA Pin',
|
||||
'wii-extension-scl-pin-label': 'I2C SCL Pin',
|
||||
'wii-extension-block-label': 'I2C Block',
|
||||
'wii-extension-speed-label': 'I2C Speed',
|
||||
'snes-extension-header-text': 'SNES Extension Configuration',
|
||||
'snes-extension-sub-header-text': '<0>Note: If the Display is enabled at the same time, this Addon will be disabled.</0> <1>Currently Supported Controllers</1> <0>SNES pad: D-Pad Supported. B = B1, A = B2, Y = B3, X = B4, L = L1, R = R1, Select = S1, Start = S2</0> <0>SNES mouse: Analog Stick Supported. Left Click = B1, Right Click = B2</0> <0>NES: D-Pad Supported. B = B1, A = B2, Select = S1, Start = S2</0>',
|
||||
'snes-extension-sub-header-text':
|
||||
'<0>Note: If the Display is enabled at the same time, this Addon will be disabled.</0> <1>Currently Supported Controllers</1> <0>SNES pad: D-Pad Supported. B = B1, A = B2, Y = B3, X = B4, L = L1, R = R1, Select = S1, Start = S2</0> <0>SNES mouse: Analog Stick Supported. Left Click = B1, Right Click = B2</0> <0>NES: D-Pad Supported. B = B1, A = B2, Select = S1, Start = S2</0>',
|
||||
'snes-extension-clock-pin-label': 'Clock Pin',
|
||||
'snes-extension-latch-pin-label': 'Latch Pin',
|
||||
'snes-extension-data-pin-label': 'Data Pin',
|
||||
'focus-mode-header-text': 'Focus Mode Configuration',
|
||||
'focus-mode-pin-label': 'Focus Mode Pin',
|
||||
'keyboard-host-header-text': 'Keyboard Host Configuration',
|
||||
'keyboard-host-sub-header-text': 'Following set the data +, - and 5V (optional) pins. Only the + and 5V pin can be configured.',
|
||||
'keyboard-host-sub-header-text':
|
||||
'Following set the data +, - and 5V (optional) pins. Only the + and 5V pin can be configured.',
|
||||
'keyboard-host-d-plus-label': 'D+',
|
||||
'keyboard-host-d-minus-label': 'D-',
|
||||
'keyboard-host-five-v-label': '5V Power (optional)',
|
||||
'pspassthrough-header-text': 'PS Passthrough',
|
||||
'pspassthrough-sub-header-text': 'Following set the data +, - and 5V (optional) pins. Only the + and 5V pin can be configured.',
|
||||
'pspassthrough-sub-header-text':
|
||||
'Following set the data +, - and 5V (optional) pins. Only the + and 5V pin can be configured.',
|
||||
'pspassthrough-d-plus-label': 'D+',
|
||||
'pspassthrough-d-minus-label': 'D-',
|
||||
'pspassthrough-five-v-label': '5V Power (optional)',
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
export default {
|
||||
"header-text": "Data Backup and Restoration",
|
||||
"sub-header-text": "Backups made from different GP2040-CE versions can be used.",
|
||||
"saved-success-message": "Saved as: {{name}}",
|
||||
"save-header-text": "Backup To File",
|
||||
"save-export-option-label": "Export {{api}} Options",
|
||||
"load-header-text": "Restore From File",
|
||||
"load-export-option-label": "Import {{api}} Options",
|
||||
"api-display-text": "Display",
|
||||
"api-gamepad-text": "Gamepad",
|
||||
"api-led-text": "LED",
|
||||
"api-ledTheme-text": "Custom LED Theme",
|
||||
"api-pinmappings-text": "Pin Mappings",
|
||||
"api-addons-text": "Add-Ons",
|
||||
'header-text': 'Data Backup and Restoration',
|
||||
'sub-header-text':
|
||||
'Backups made from different GP2040-CE versions can be used.',
|
||||
'saved-success-message': 'Saved as: {{name}}',
|
||||
'save-header-text': 'Backup To File',
|
||||
'save-export-option-label': 'Export {{api}} Options',
|
||||
'load-header-text': 'Restore From File',
|
||||
'load-export-option-label': 'Import {{api}} Options',
|
||||
'api-display-text': 'Display',
|
||||
'api-gamepad-text': 'Gamepad',
|
||||
'api-led-text': 'LED',
|
||||
'api-ledTheme-text': 'Custom LED Theme',
|
||||
'api-pinmappings-text': 'Pin Mappings',
|
||||
'api-addons-text': 'Add-Ons',
|
||||
};
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
export default {
|
||||
"brand-text": "GP2040-CE",
|
||||
"button-clear-all-label": "Clear All",
|
||||
"button-delete-color-label": "Delete Color",
|
||||
"button-dismiss-label": "Dismiss",
|
||||
"button-load-label": "Load",
|
||||
"button-reset-settings-label": "Reset Settings",
|
||||
"button-save-color-label": "Save Color",
|
||||
"button-save-label": "Save",
|
||||
"button-set-all-to-color-label": "Set All To Color",
|
||||
"button-set-gradient-label": "Set Gradient",
|
||||
"button-set-pressed-gradient-label": "Set Pressed Gradient",
|
||||
"button-verify-save-label": "Verify & Save",
|
||||
"saved-success-message": "Saved! Please Restart Your Device",
|
||||
"saved-error-message": "Unable to Save",
|
||||
'errors': {
|
||||
"required": "required",
|
||||
"conflict": "conflict",
|
||||
"invalid": "invalid",
|
||||
"used": "used",
|
||||
"validation-error": "Validation errors, see above",
|
||||
'brand-text': 'GP2040-CE',
|
||||
'button-clear-all-label': 'Clear All',
|
||||
'button-delete-color-label': 'Delete Color',
|
||||
'button-dismiss-label': 'Dismiss',
|
||||
'button-load-label': 'Load',
|
||||
'button-reset-settings-label': 'Reset Settings',
|
||||
'button-save-color-label': 'Save Color',
|
||||
'button-save-label': 'Save',
|
||||
'button-set-all-to-color-label': 'Set All To Color',
|
||||
'button-set-gradient-label': 'Set Gradient',
|
||||
'button-set-pressed-gradient-label': 'Set Pressed Gradient',
|
||||
'button-verify-save-label': 'Verify & Save',
|
||||
'saved-success-message': 'Saved! Please Restart Your Device',
|
||||
'saved-error-message': 'Unable to Save',
|
||||
errors: {
|
||||
required: 'required',
|
||||
conflict: 'conflict',
|
||||
invalid: 'invalid',
|
||||
used: 'used',
|
||||
'validation-error': 'Validation errors, see above',
|
||||
},
|
||||
'switch-enabled': 'Enabled',
|
||||
'lock-oled-screen': 'Lock OLED Screen',
|
||||
'lock-rgb-led': 'Lock RGB LED',
|
||||
'lock-buttons': 'Lock Buttons',
|
||||
'loading-text': 'Loading...'
|
||||
'loading-text': 'Loading...',
|
||||
};
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
export default {
|
||||
"language-selector": {
|
||||
"en-GB": "British English",
|
||||
"en": "English",
|
||||
"nl": "Dutch",
|
||||
'language-selector': {
|
||||
'en-GB': 'British English',
|
||||
en: 'English',
|
||||
nl: 'Dutch',
|
||||
},
|
||||
"color-scheme": {
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"auto": "Auto",
|
||||
'color-scheme': {
|
||||
dark: 'Dark',
|
||||
light: 'Light',
|
||||
auto: 'Auto',
|
||||
},
|
||||
'keyboard-mapper': {
|
||||
'key-header': 'Key',
|
||||
'error-conflict': 'Key {{key}} is already assigned',
|
||||
'error-invalid': '{{key}} is invalid for this board',
|
||||
},
|
||||
"keyboard-mapper": {
|
||||
"key-header": "Key",
|
||||
"error-conflict": "Key {{key}} is already assigned",
|
||||
"error-invalid": "{{key}} is invalid for this board",
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
export default {
|
||||
"header-text": "Custom LED Theme",
|
||||
"sub-header-text": "Here you can enable and configure a custom LED theme.<br />The custom theme will be selectable using the Next and Previous Animation shortcuts on your controller.",
|
||||
"list-text": "<1>Click a button to bring up the normal and pressed color selection.</1> <1>Click on the controller background to dismiss the color selection.</1> <1>Right-click a button to preview the button's pressed color.</1>",
|
||||
"led-layout-label": "Preview Layout",
|
||||
"has-custom-theme-label": "Enable",
|
||||
"modal-title": "Confirm Clear Custom Theme",
|
||||
"modal-body": "Are you sure you would like to clear your current custom LED theme?",
|
||||
"modal-no": "No",
|
||||
"modal-yes": "Yes",
|
||||
'header-text': 'Custom LED Theme',
|
||||
'sub-header-text':
|
||||
'Here you can enable and configure a custom LED theme.<br />The custom theme will be selectable using the Next and Previous Animation shortcuts on your controller.',
|
||||
'list-text':
|
||||
"<1>Click a button to bring up the normal and pressed color selection.</1> <1>Click on the controller background to dismiss the color selection.</1> <1>Right-click a button to preview the button's pressed color.</1>",
|
||||
'led-layout-label': 'Preview Layout',
|
||||
'has-custom-theme-label': 'Enable',
|
||||
'modal-title': 'Confirm Clear Custom Theme',
|
||||
'modal-body':
|
||||
'Are you sure you would like to clear your current custom LED theme?',
|
||||
'modal-no': 'No',
|
||||
'modal-yes': 'Yes',
|
||||
};
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
export default {
|
||||
'header-text': 'Display Configuration',
|
||||
'sub-header-text': 'A monochrome display can be used to show controller status and button activity. Ensure your display module has the following attributes:',
|
||||
'list-text': '<1>Monochrome display with 128x64 resolution</1> <1>Uses I2C with a SSD1306, SH1106, SH1107 or other compatible display IC</1> <1>Supports 3.3v operation</1>',
|
||||
'table': {
|
||||
'header': 'Use these tables to determine which I2C block to select based on the configured SDA and SCL pins:',
|
||||
'sub-header-text':
|
||||
'A monochrome display can be used to show controller status and button activity. Ensure your display module has the following attributes:',
|
||||
'list-text':
|
||||
'<1>Monochrome display with 128x64 resolution</1> <1>Uses I2C with a SSD1306, SH1106, SH1107 or other compatible display IC</1> <1>Supports 3.3v operation</1>',
|
||||
table: {
|
||||
header:
|
||||
'Use these tables to determine which I2C block to select based on the configured SDA and SCL pins:',
|
||||
'sda-scl-pins-header': 'SDA/SCL Pins',
|
||||
'i2c-block-header': 'I2C Block',
|
||||
},
|
||||
'form': {
|
||||
form: {
|
||||
'i2c-block-label': 'I2C Block',
|
||||
'sda-pin-label': 'SDA Pin',
|
||||
'scl-pin-label': 'SCL Pin',
|
||||
|
||||
@@ -9,5 +9,5 @@ export default {
|
||||
'memory-static-allocations-text': 'Static Allocations',
|
||||
'sub-header-text': 'Please select a menu option to proceed.',
|
||||
'system-stats-header-text': 'System Stats',
|
||||
'version-text': 'Version'
|
||||
'version-text': 'Version',
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
"header-text": "Keyboard Mapping",
|
||||
"sub-header-text": "Use the form below to reconfigure your button-to-key mapping.",
|
||||
'header-text': 'Keyboard Mapping',
|
||||
'sub-header-text':
|
||||
'Use the form below to reconfigure your button-to-key mapping.',
|
||||
};
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
export default {
|
||||
"rgb": {
|
||||
"header-text": "RGB LED Configuration",
|
||||
"data-pin-label": "Data Pin (-1 for disabled)",
|
||||
"led-format-label": "LED Format",
|
||||
"led-layout-label": "LED Layout",
|
||||
"leds-per-button-label": "LEDs Per Button",
|
||||
"led-brightness-maximum-label": "Max Brightness",
|
||||
"led-brightness-steps-label": "Brightness Steps",
|
||||
rgb: {
|
||||
'header-text': 'RGB LED Configuration',
|
||||
'data-pin-label': 'Data Pin (-1 for disabled)',
|
||||
'led-format-label': 'LED Format',
|
||||
'led-layout-label': 'LED Layout',
|
||||
'leds-per-button-label': 'LEDs Per Button',
|
||||
'led-brightness-maximum-label': 'Max Brightness',
|
||||
'led-brightness-steps-label': 'Brightness Steps',
|
||||
},
|
||||
"player": {
|
||||
"header-text": "Player LEDs (XInput)",
|
||||
"pwm-sub-header-text": "For PWM LEDs, set each LED to a dedicated GPIO pin.",
|
||||
"rgb-sub-header-text": "For RGB LEDs, the indexes must be after the last LED button defined in <1>RGB LED Button Order</1> section and likely <3>starts at index {{rgbLedStartIndex}}</3>.",
|
||||
"pled-type-label": "Player LED Type",
|
||||
"pled-type-off": "Off",
|
||||
"pled-type-pwm": "PWM",
|
||||
"pled-type-rgb": "RGB",
|
||||
"pled-color-label": "RGB PLED Color",
|
||||
player: {
|
||||
'header-text': 'Player LEDs (XInput)',
|
||||
'pwm-sub-header-text':
|
||||
'For PWM LEDs, set each LED to a dedicated GPIO pin.',
|
||||
'rgb-sub-header-text':
|
||||
'For RGB LEDs, the indexes must be after the last LED button defined in <1>RGB LED Button Order</1> section and likely <3>starts at index {{rgbLedStartIndex}}</3>.',
|
||||
'pled-type-label': 'Player LED Type',
|
||||
'pled-type-off': 'Off',
|
||||
'pled-type-pwm': 'PWM',
|
||||
'pled-type-rgb': 'RGB',
|
||||
'pled-color-label': 'RGB PLED Color',
|
||||
},
|
||||
'pled-pin-label': 'PLED #{{pin}} Pin',
|
||||
'pled-index-label': 'PLED #{{index}} Index',
|
||||
'rgb-order': {
|
||||
'header-text': 'RGB LED Button Order',
|
||||
'sub-header-text':
|
||||
'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.',
|
||||
'sub-header1-text':
|
||||
'Drag and drop list items to assign and reorder the RGB LEDs.',
|
||||
'available-header-text': 'Available Buttons',
|
||||
'assigned-header-text': 'Assigned Buttons',
|
||||
},
|
||||
"pled-pin-label": "PLED #{{pin}} Pin",
|
||||
"pled-index-label": "PLED #{{index}} Index",
|
||||
"rgb-order": {
|
||||
"header-text": "RGB LED Button Order",
|
||||
"sub-header-text": "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.",
|
||||
"sub-header1-text": "Drag and drop list items to assign and reorder the RGB LEDs.",
|
||||
"available-header-text": "Available Buttons",
|
||||
"assigned-header-text": "Assigned Buttons",
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
'led-config-label': 'LED Configuration',
|
||||
'links-label': 'Links',
|
||||
'pin-mapping-label': 'Pin Mapping',
|
||||
"profile-settings-label": "Profile Settings",
|
||||
'profile-settings-label': 'Profile Settings',
|
||||
'reboot-label': 'Reboot',
|
||||
'reboot-modal-body': 'Select a mode to reboot to',
|
||||
'reboot-modal-button-bootsel-label': 'USB (BOOTSEL)',
|
||||
@@ -23,5 +23,5 @@ export default {
|
||||
'reboot-modal-label': 'Reboot?',
|
||||
'reboot-modal-success': 'Done rebooting, this browser tab can now be closed.',
|
||||
'resetSettings-label': 'Reset Settings',
|
||||
'settings-label': 'Settings'
|
||||
'settings-label': 'Settings',
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
export default {
|
||||
"header-text": "Pin Mapping",
|
||||
"sub-header-text": "Use the form below to reconfigure your button-to-pin mapping.",
|
||||
"alert-text": "Mapping buttons to pins that aren't connected or available can leave the device in non-functional state. To clear the the invalid configuration go to the <1>Reset Settings</1> page.",
|
||||
"pin-header-label": "Pin",
|
||||
"errors": {
|
||||
"conflict": "Pin {{pin}} is already assigned to {{conflictedMappings}}",
|
||||
"required": "{{button}} is required",
|
||||
"invalid": "{{pin}} is invalid for this board",
|
||||
"used": "{{pin}} is already assigned to another feature"
|
||||
}
|
||||
'header-text': 'Pin Mapping',
|
||||
'sub-header-text':
|
||||
'Use the form below to reconfigure your button-to-pin mapping.',
|
||||
'alert-text':
|
||||
"Mapping buttons to pins that aren't connected or available can leave the device in non-functional state. To clear the the invalid configuration go to the <1>Reset Settings</1> page.",
|
||||
'pin-header-label': 'Pin',
|
||||
errors: {
|
||||
conflict: 'Pin {{pin}} is already assigned to {{conflictedMappings}}',
|
||||
required: '{{button}} is required',
|
||||
invalid: '{{pin}} is invalid for this board',
|
||||
used: '{{pin}} is already assigned to another feature',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export default {
|
||||
"header-text": "Profiles",
|
||||
"profile-pins-desc": "This page allows three additional button mappings to be configured as profiles 2 through 4, which can be loaded via hotkey. (The first profile is the core configuration from the Pin Mapping page.) A physical layout of the pins:",
|
||||
"profile-1": "Profile 1",
|
||||
"profile-2": "Profile 2",
|
||||
"profile-3": "Profile 3",
|
||||
"profile-4": "Profile 4",
|
||||
"profile-pins-warning": "Try to avoid changing the buttons/directions used for your switch profile hotkeys, or else it will get hard to understand what profile you are selecting!",
|
||||
}
|
||||
'header-text': 'Profiles',
|
||||
'profile-pins-desc':
|
||||
'This page allows three additional button mappings to be configured as profiles 2 through 4, which can be loaded via hotkey. (The first profile is the core configuration from the Pin Mapping page.) A physical layout of the pins:',
|
||||
'profile-1': 'Profile 1',
|
||||
'profile-2': 'Profile 2',
|
||||
'profile-3': 'Profile 3',
|
||||
'profile-4': 'Profile 4',
|
||||
'profile-pins-warning':
|
||||
'Try to avoid changing the buttons/directions used for your switch profile hotkeys, or else it will get hard to understand what profile you are selecting!',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default {
|
||||
'header-text': 'Reset Settings',
|
||||
'sub-header-text': '<0 className="card-text"> This option resets all saved configurations on your controller. Use this option as a last resort or when trying to diagnose odd issues with your controller. </0> <0 className="card-text">This process will automatically reset the controller.</0>',
|
||||
'sub-header-text':
|
||||
'<0 className="card-text"> This option resets all saved configurations on your controller. Use this option as a last resort or when trying to diagnose odd issues with your controller. </0> <0 className="card-text">This process will automatically reset the controller.</0>',
|
||||
'confirm-text': 'Are you sure you want to reset saved configuration?',
|
||||
};
|
||||
|
||||
@@ -3,36 +3,40 @@ export default {
|
||||
'input-mode-label': 'Input Mode',
|
||||
'input-mode-extra-label': 'Switch Touchpad and Share',
|
||||
'input-mode-options': {
|
||||
'xinput': "XInput",
|
||||
'nintendo-switch': "Nintendo Switch",
|
||||
'ps3': "PS3/DirectInput",
|
||||
'keyboard': "Keyboard",
|
||||
'ps4': "PS4"
|
||||
xinput: 'XInput',
|
||||
'nintendo-switch': 'Nintendo Switch',
|
||||
ps3: 'PS3/DirectInput',
|
||||
keyboard: 'Keyboard',
|
||||
ps4: 'PS4',
|
||||
},
|
||||
'ps4-mode-options': {
|
||||
'controller': "Controller",
|
||||
'arcadestick': "Arcade Stick",
|
||||
controller: 'Controller',
|
||||
arcadestick: 'Arcade Stick',
|
||||
},
|
||||
'd-pad-mode-label': 'D-Pad Mode',
|
||||
'd-pad-mode-options': {
|
||||
"d-pad": "D-Pad",
|
||||
"left-analog": "Left Analog",
|
||||
"right-analog": "Right Analog"
|
||||
'd-pad': 'D-Pad',
|
||||
'left-analog': 'Left Analog',
|
||||
'right-analog': 'Right Analog',
|
||||
},
|
||||
'socd-cleaning-mode-label': 'SOCD Cleaning Mode',
|
||||
'socd-cleaning-mode-note': 'Note: PS4, PS3 and Nintendo Switch modes do not support setting SOCD Cleaning to Off and will default to Neutral SOCD Cleaning mode.',
|
||||
'socd-cleaning-mode-note':
|
||||
'Note: PS4, PS3 and Nintendo Switch modes do not support setting SOCD Cleaning to Off and will default to Neutral SOCD Cleaning mode.',
|
||||
'socd-cleaning-mode-options': {
|
||||
'up-priority': "Up Priority",
|
||||
'neutral': "Neutral",
|
||||
'up-priority': 'Up Priority',
|
||||
neutral: 'Neutral',
|
||||
'last-win': 'Last Win',
|
||||
'first-win': 'First Win',
|
||||
'off': 'Off'
|
||||
off: 'Off',
|
||||
},
|
||||
'profile-number-label': 'Profile Number',
|
||||
'ps4-compatibility-label': 'For <strong>PS5 compatibility</strong>, use "Arcade Stick" and enable PS Passthrough add-on<br/>For <strong>PS4 support</strong>, use "Controller" and enable PS4 Mode add-on if you have the necessary files',
|
||||
'ps4-compatibility-label':
|
||||
'For <strong>PS5 compatibility</strong>, use "Arcade Stick" and enable PS Passthrough add-on<br/>For <strong>PS4 support</strong>, use "Controller" and enable PS4 Mode add-on if you have the necessary files',
|
||||
'hotkey-settings-label': 'Hotkey Settings',
|
||||
'hotkey-settings-sub-header': "The <1>Fn</1> slider provides a mappable Function button in the <3 exact='true' to='/pin-mapping'>Pin Mapping</3> page. By selecting the <1>Fn</1> slider option, the Function button must be held along with the selected hotkey settings.<5 />Additionally, select <1>None</1> from the dropdown to unassign any button.",
|
||||
'hotkey-settings-warning': 'Function button is not mapped. The Fn slider will be disabled.',
|
||||
'hotkey-settings-sub-header':
|
||||
"The <1>Fn</1> slider provides a mappable Function button in the <3 exact='true' to='/pin-mapping'>Pin Mapping</3> page. By selecting the <1>Fn</1> slider option, the Function button must be held along with the selected hotkey settings.<5 />Additionally, select <1>None</1> from the dropdown to unassign any button.",
|
||||
'hotkey-settings-warning':
|
||||
'Function button is not mapped. The Fn slider will be disabled.',
|
||||
'hotkey-actions': {
|
||||
'no-action': 'No Action',
|
||||
'dpad-digital': 'Dpad Digital',
|
||||
@@ -60,13 +64,14 @@ export default {
|
||||
},
|
||||
'forced-setup-mode-label': 'Forced Setup Mode',
|
||||
'forced-setup-mode-options': {
|
||||
'off': 'Off',
|
||||
off: 'Off',
|
||||
'disable-input-mode': 'Disable Input Mode',
|
||||
'disable-web-config': 'Disable Web Config',
|
||||
'disable-both': 'Disable Both'
|
||||
'disable-both': 'Disable Both',
|
||||
},
|
||||
'forced-setup-mode-modal-title': 'Forced Setup Mode Warning',
|
||||
'forced-setup-mode-modal-body': 'If you reboot to Controller mode after saving, you will no longer have access to the web-config. Please type "<strong>{{warningCheckText}}</strong>" below to unlock the Save button if you fully acknowledge this and intend it. Clicking on Dismiss will revert this setting which then is to be saved.',
|
||||
'forced-setup-mode-modal-body':
|
||||
'If you reboot to Controller mode after saving, you will no longer have access to the web-config. Please type "<strong>{{warningCheckText}}</strong>" below to unlock the Save button if you fully acknowledge this and intend it. Clicking on Dismiss will revert this setting which then is to be saved.',
|
||||
'4-way-joystick-mode-label': '4-Way Joystick Mode',
|
||||
'lock-hotkeys-label': 'Lock Hotkeys',
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,16 +6,36 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import Section from '../Components/Section';
|
||||
import WebApi from '../Services/WebApi';
|
||||
|
||||
const FILE_EXTENSION = ".gp2040"
|
||||
const FILENAME = "gp2040ce_backup_{DATE}" + FILE_EXTENSION;
|
||||
const FILE_EXTENSION = '.gp2040';
|
||||
const FILENAME = 'gp2040ce_backup_{DATE}' + FILE_EXTENSION;
|
||||
|
||||
const API_BINDING = {
|
||||
"display": {label: "Display", get: WebApi.getDisplayOptions, set: WebApi.setDisplayOptions},
|
||||
"gamepad": {label: "Gamepad", get: WebApi.getGamepadOptions, set: WebApi.setGamepadOptions},
|
||||
"led": {label: "LED", get: WebApi.getLedOptions, set: WebApi.setLedOptions},
|
||||
"ledTheme": {label: "Custom LED Theme", get: WebApi.getCustomTheme, set: WebApi.setCustomTheme},
|
||||
"pinmappings": {label: "Pin Mappings", get: WebApi.getPinMappings, set: WebApi.setPinMappings},
|
||||
"addons": {label: "Add-Ons", get: WebApi.getAddonsOptions, set: WebApi.setAddonsOptions},
|
||||
display: {
|
||||
label: 'Display',
|
||||
get: WebApi.getDisplayOptions,
|
||||
set: WebApi.setDisplayOptions,
|
||||
},
|
||||
gamepad: {
|
||||
label: 'Gamepad',
|
||||
get: WebApi.getGamepadOptions,
|
||||
set: WebApi.setGamepadOptions,
|
||||
},
|
||||
led: { label: 'LED', get: WebApi.getLedOptions, set: WebApi.setLedOptions },
|
||||
ledTheme: {
|
||||
label: 'Custom LED Theme',
|
||||
get: WebApi.getCustomTheme,
|
||||
set: WebApi.setCustomTheme,
|
||||
},
|
||||
pinmappings: {
|
||||
label: 'Pin Mappings',
|
||||
get: WebApi.getPinMappings,
|
||||
set: WebApi.setPinMappings,
|
||||
},
|
||||
addons: {
|
||||
label: 'Add-Ons',
|
||||
get: WebApi.getAddonsOptions,
|
||||
set: WebApi.setAddonsOptions,
|
||||
},
|
||||
// new api, add it here
|
||||
// "example": {label: "Example", get: WebApi.getNewAPI, set: WebApi.setNewAPI},
|
||||
};
|
||||
@@ -24,7 +44,7 @@ export default function BackupPage() {
|
||||
const inputFileSelect = useRef();
|
||||
|
||||
const [optionState, setOptionStateData] = useState({});
|
||||
const [checkValues, setCheckValues] = useState({}); // lazy approach
|
||||
const [checkValues, setCheckValues] = useState({}); // lazy approach
|
||||
|
||||
const [noticeMessage, setNoticeMessage] = useState('');
|
||||
const [saveMessage, setSaveMessage] = useState('');
|
||||
@@ -51,12 +71,12 @@ export default function BackupPage() {
|
||||
defaults[`import_${key}`] = true;
|
||||
}
|
||||
return defaults;
|
||||
};
|
||||
}
|
||||
setCheckValues(getDefaultValues());
|
||||
}, []);
|
||||
|
||||
const validateValues = (data, nextData) => {
|
||||
if (typeof data != "object" || typeof nextData != "object") {
|
||||
if (typeof data != 'object' || typeof nextData != 'object') {
|
||||
// invalid data types
|
||||
return {};
|
||||
}
|
||||
@@ -64,8 +84,12 @@ export default function BackupPage() {
|
||||
let validated = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const nextDataValue = nextData[key];
|
||||
if (nextDataValue !== null && typeof nextDataValue !== 'undefined' && typeof value == typeof nextDataValue) {
|
||||
if (typeof nextDataValue == "object") {
|
||||
if (
|
||||
nextDataValue !== null &&
|
||||
typeof nextDataValue !== 'undefined' &&
|
||||
typeof value == typeof nextDataValue
|
||||
) {
|
||||
if (typeof nextDataValue == 'object') {
|
||||
validated[key] = validateValues(value, nextDataValue);
|
||||
} else {
|
||||
validated[key] = nextDataValue;
|
||||
@@ -74,7 +98,7 @@ export default function BackupPage() {
|
||||
}
|
||||
|
||||
return validated;
|
||||
}
|
||||
};
|
||||
|
||||
const setOptionsToAPIStorage = async (options) => {
|
||||
for (const [key, func] of Object.entries(API_BINDING)) {
|
||||
@@ -84,19 +108,19 @@ export default function BackupPage() {
|
||||
console.log(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (ev) => {
|
||||
const id = ev.nativeEvent.target.id;
|
||||
let nextCheckValue = {};
|
||||
nextCheckValue[id] = !checkValues[id];
|
||||
setCheckValues(checkValues => ({...checkValues, ...nextCheckValue}));
|
||||
}
|
||||
setCheckValues((checkValues) => ({ ...checkValues, ...nextCheckValue }));
|
||||
};
|
||||
|
||||
const handleSave = async (values) => {
|
||||
let exportData = {};
|
||||
for (const [key, value] of Object.entries(checkValues)) {
|
||||
if (key.match("export_") && (value != null || value !== undefined)) {
|
||||
if (key.match('export_') && (value != null || value !== undefined)) {
|
||||
let skey = key.slice(7, key.length);
|
||||
if (optionState[skey] !== undefined || optionState[skey] != null) {
|
||||
exportData[skey] = optionState[skey];
|
||||
@@ -105,16 +129,16 @@ export default function BackupPage() {
|
||||
}
|
||||
|
||||
const fileDate = new Date().toISOString().replace(/[^0-9]/g, '');
|
||||
const name = FILENAME.replace("{DATE}", fileDate);
|
||||
const name = FILENAME.replace('{DATE}', fileDate);
|
||||
const json = JSON.stringify(exportData);
|
||||
const file = new Blob([json], { type: 'text/json;charset=utf-8' });
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(file);
|
||||
a.download = name;
|
||||
a.innerHTML = "Save Backup";
|
||||
a.innerHTML = 'Save Backup';
|
||||
|
||||
let container = document.getElementById("root");
|
||||
let container = document.getElementById('root');
|
||||
container.appendChild(a);
|
||||
|
||||
a.click();
|
||||
@@ -141,11 +165,11 @@ export default function BackupPage() {
|
||||
const fileName = input.files[0].name;
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
reader.onload = function () {
|
||||
let fileData = undefined;
|
||||
try {
|
||||
fileData = JSON.parse(reader.result);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
// error dialog
|
||||
setNoticeMessage(`Failed to parse data for ${fileName}!`);
|
||||
return;
|
||||
@@ -168,14 +192,14 @@ export default function BackupPage() {
|
||||
// filter by known values
|
||||
let filteredData = {};
|
||||
for (const [key, value] of Object.entries(checkValues)) {
|
||||
if (key.match("import_") && (value != null || value !== undefined)) {
|
||||
if (key.match('import_') && (value != null || value !== undefined)) {
|
||||
let skey = key.slice(7, key.length);
|
||||
if (newData[skey] !== undefined || newData[skey] != null) {
|
||||
filteredData[skey] = newData[skey];
|
||||
}
|
||||
}
|
||||
}
|
||||
const nextOptions = {...optionState, ...filteredData};
|
||||
const nextOptions = { ...optionState, ...filteredData };
|
||||
setOptionStateData(nextOptions);
|
||||
|
||||
// write to internal storage
|
||||
@@ -191,9 +215,9 @@ export default function BackupPage() {
|
||||
};
|
||||
reader.onerror = () => {
|
||||
setNoticeMessage(`Error occured while reading ${fileName}.`);
|
||||
}
|
||||
};
|
||||
reader.readAsText(input.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -202,41 +226,38 @@ export default function BackupPage() {
|
||||
</Section>
|
||||
<Section title={t('BackupPage:save-header-text')}>
|
||||
<Col>
|
||||
<Form.Group className={"row mb-3"}>
|
||||
<div className={"col-sm-4"}>
|
||||
{Object.entries(API_BINDING).map(api =>
|
||||
<Form.Group className={'row mb-3'}>
|
||||
<div className={'col-sm-4'}>
|
||||
{Object.entries(API_BINDING).map((api) => (
|
||||
<Form.Check
|
||||
id={`export_${api[0]}`}
|
||||
key={`export_${api[0]}`}
|
||||
label={t('BackupPage:save-export-option-label', {
|
||||
api: t(`BackupPage:api-${api[0]}-text`)
|
||||
api: t(`BackupPage:api-${api[0]}-text`),
|
||||
})}
|
||||
type={"checkbox"}
|
||||
type={'checkbox'}
|
||||
checked={checkValues[`export_${api[0]}`] ?? false}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</Form.Group>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row"
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={handleSave}
|
||||
>
|
||||
<Button type="submit" onClick={handleSave}>
|
||||
{t('Common:button-save-label')}
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
height: '100%',
|
||||
paddingLeft: 24,
|
||||
fontWeight: 600,
|
||||
color: "darkcyan",
|
||||
alignSelf: "center"
|
||||
color: 'darkcyan',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
{saveMessage ? saveMessage : null}
|
||||
@@ -246,33 +267,33 @@ export default function BackupPage() {
|
||||
</Section>
|
||||
<Section title={t('BackupPage:load-header-text')}>
|
||||
<Col>
|
||||
<Form.Group className={"row mb-3"}>
|
||||
<div className={"col-sm-4"}>
|
||||
{Object.entries(API_BINDING).map(api =>
|
||||
<Form.Group className={'row mb-3'}>
|
||||
<div className={'col-sm-4'}>
|
||||
{Object.entries(API_BINDING).map((api) => (
|
||||
<Form.Check
|
||||
id={`import_${api[0]}`}
|
||||
key={`import_${api[0]}`}
|
||||
label={t('BackupPage:load-export-option-label', {
|
||||
api: t(`BackupPage:api-${api[0]}-text`)
|
||||
api: t(`BackupPage:api-${api[0]}-text`),
|
||||
})}
|
||||
type={"checkbox"}
|
||||
type={'checkbox'}
|
||||
checked={checkValues[`import_${api[0]}`] ?? false}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</Form.Group>
|
||||
<input
|
||||
ref={inputFileSelect}
|
||||
type={"file"}
|
||||
type={'file'}
|
||||
accept={FILE_EXTENSION}
|
||||
style={{display: "none"}}
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileSelect.bind(this)}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row"
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -284,15 +305,17 @@ export default function BackupPage() {
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
height: '100%',
|
||||
paddingLeft: 24,
|
||||
fontWeight: 600,
|
||||
color: "darkcyan",
|
||||
alignSelf: "center"
|
||||
color: 'darkcyan',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
<span>{loadMessage ? loadMessage : null}</span>
|
||||
<span style={{color: "red", fontWeight: "bold"}}>{noticeMessage ? noticeMessage : null}</span>
|
||||
<span style={{ color: 'red', fontWeight: 'bold' }}>
|
||||
{noticeMessage ? noticeMessage : null}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
@@ -11,14 +11,21 @@ import Popover from 'react-bootstrap/Popover';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Stack from 'react-bootstrap/Stack';
|
||||
import { SketchPicker } from '@hello-pangea/color-picker';
|
||||
import Gradient from "javascript-color-gradient";
|
||||
import Gradient from 'javascript-color-gradient';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import FormSelect from '../Components/FormSelect';
|
||||
import Section from '../Components/Section';
|
||||
import WebApi from '../Services/WebApi';
|
||||
import { BUTTONS, MAIN_BUTTONS, AUX_BUTTONS, KEYBOARD_LAYOUT, STICK_LAYOUT, STICKLESS_LAYOUT } from '../Data/Buttons';
|
||||
import {
|
||||
BUTTONS,
|
||||
MAIN_BUTTONS,
|
||||
AUX_BUTTONS,
|
||||
KEYBOARD_LAYOUT,
|
||||
STICK_LAYOUT,
|
||||
STICKLESS_LAYOUT,
|
||||
} from '../Data/Buttons';
|
||||
import LEDColors from '../Data/LEDColors';
|
||||
|
||||
import './CustomThemePage.scss';
|
||||
@@ -45,25 +52,40 @@ const BUTTON_LAYOUTS = [
|
||||
];
|
||||
|
||||
const defaultCustomTheme = Object.keys(BUTTONS.gp2040)
|
||||
?.filter(p => p !== 'label' && p !== 'value')
|
||||
?.filter((p) => p !== 'label' && p !== 'value')
|
||||
.reduce((a, p) => {
|
||||
a[p] = { normal: '#000000', pressed: '#000000' };
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
defaultCustomTheme['ALL'] = { normal: '#000000', pressed: '#000000' };
|
||||
defaultCustomTheme['GRADIENT NORMAL'] = { normal: '#00ffff', pressed: '#ff00ff' };
|
||||
defaultCustomTheme['GRADIENT PRESSED'] = { normal: '#ff00ff', pressed: '#00ffff' };
|
||||
defaultCustomTheme['GRADIENT NORMAL'] = {
|
||||
normal: '#00ffff',
|
||||
pressed: '#ff00ff',
|
||||
};
|
||||
defaultCustomTheme['GRADIENT PRESSED'] = {
|
||||
normal: '#ff00ff',
|
||||
pressed: '#00ffff',
|
||||
};
|
||||
|
||||
const specialButtons = ['ALL', 'GRADIENT NORMAL', 'GRADIENT PRESSED'];
|
||||
|
||||
const LEDButton = ({ id, name, buttonType, buttonColor, buttonPressedColor, className, labelUnder, onClick, ...props }) => {
|
||||
const LEDButton = ({
|
||||
id,
|
||||
name,
|
||||
buttonType,
|
||||
buttonColor,
|
||||
buttonPressedColor,
|
||||
className,
|
||||
labelUnder,
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
const [pressed, setPressed] = useState(false);
|
||||
|
||||
const handlePressedShow = (e) => {
|
||||
// Show pressed state on right-click
|
||||
if (e.button === 2)
|
||||
setPressed(true);
|
||||
if (e.button === 2) setPressed(true);
|
||||
};
|
||||
|
||||
const handlePressedHide = (e) => {
|
||||
@@ -81,13 +103,15 @@ const LEDButton = ({ id, name, buttonType, buttonColor, buttonPressedColor, clas
|
||||
onMouseLeave={(e) => handlePressedHide(e)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<span className={`button-label ${labelUnder ? 'under' : ''}`}>{name}</span>
|
||||
<span className={`button-label ${labelUnder ? 'under' : ''}`}>
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ledColors = LEDColors.map(c => ({ title: c.name, color: c.value }));
|
||||
const customColors = (colors) => colors.map(c => ({ title: c, color: c }));
|
||||
const ledColors = LEDColors.map((c) => ({ title: c.name, color: c.value }));
|
||||
const customColors = (colors) => colors.map((c) => ({ title: c, color: c }));
|
||||
|
||||
const CustomThemePage = () => {
|
||||
const {
|
||||
@@ -101,7 +125,7 @@ const CustomThemePage = () => {
|
||||
setGradientNormalColor2,
|
||||
setGradientPressedColor1,
|
||||
setGradientPressedColor2,
|
||||
setSavedColors
|
||||
setSavedColors,
|
||||
} = useContext(AppContext);
|
||||
const [saveMessage, setSaveMessage] = useState('');
|
||||
const [ledLayout, setLedLayout] = useState(0);
|
||||
@@ -113,7 +137,10 @@ const CustomThemePage = () => {
|
||||
const [ledOverlayTarget, setLedOverlayTarget] = useState(document.body);
|
||||
const [pickerVisible, setPickerVisible] = useState(false);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [presetColors, setPresetColors] = useState([...ledColors, ...customColors(savedColors)]);
|
||||
const [presetColors, setPresetColors] = useState([
|
||||
...ledColors,
|
||||
...customColors(savedColors),
|
||||
]);
|
||||
const { buttonLabelType } = buttonLabels;
|
||||
const { setLoading } = useContext(AppContext);
|
||||
|
||||
@@ -135,8 +162,7 @@ const CustomThemePage = () => {
|
||||
|
||||
const deleteCurrentColor = () => {
|
||||
const colorIndex = savedColors.indexOf(selectedColor.hex);
|
||||
if (colorIndex < 0)
|
||||
return;
|
||||
if (colorIndex < 0) return;
|
||||
|
||||
const newColors = [...savedColors];
|
||||
newColors.splice(colorIndex, 1);
|
||||
@@ -152,9 +178,13 @@ const CustomThemePage = () => {
|
||||
const handleLedColorChange = (c) => {
|
||||
if (selectedButton) {
|
||||
if (selectedButton === 'ALL') {
|
||||
Object.keys(customTheme).filter(b => b === 'ALL' || specialButtons.indexOf(b) === -1).forEach(p => customTheme[p][pickerType.type] = c.hex);
|
||||
}
|
||||
else if (selectedButton === 'GRADIENT NORMAL' || selectedButton === 'GRADIENT PRESSED') {
|
||||
Object.keys(customTheme)
|
||||
.filter((b) => b === 'ALL' || specialButtons.indexOf(b) === -1)
|
||||
.forEach((p) => (customTheme[p][pickerType.type] = c.hex));
|
||||
} else if (
|
||||
selectedButton === 'GRADIENT NORMAL' ||
|
||||
selectedButton === 'GRADIENT PRESSED'
|
||||
) {
|
||||
customTheme[selectedButton][pickerType.type] = c.hex;
|
||||
|
||||
// Apply the gradient across action buttons only, 7-8 columns
|
||||
@@ -162,37 +192,57 @@ const CustomThemePage = () => {
|
||||
const count = matrix.length;
|
||||
|
||||
let steps = [customTheme[selectedButton].normal];
|
||||
steps.push(...new Gradient()
|
||||
.setColorGradient(customTheme[selectedButton].normal, customTheme[selectedButton].pressed)
|
||||
.setMidpoint(count - 2)
|
||||
.getColors()
|
||||
steps.push(
|
||||
...new Gradient()
|
||||
.setColorGradient(
|
||||
customTheme[selectedButton].normal,
|
||||
customTheme[selectedButton].pressed,
|
||||
)
|
||||
.setMidpoint(count - 2)
|
||||
.getColors(),
|
||||
);
|
||||
steps.push(customTheme[selectedButton].pressed);
|
||||
|
||||
if (selectedButton === 'GRADIENT NORMAL') {
|
||||
matrix.forEach((r, i) => r.filter(b => !!b).forEach(b => customTheme[b] = { normal: steps[i], pressed: customTheme[b].pressed }));
|
||||
matrix.forEach((r, i) =>
|
||||
r
|
||||
.filter((b) => !!b)
|
||||
.forEach(
|
||||
(b) =>
|
||||
(customTheme[b] = {
|
||||
normal: steps[i],
|
||||
pressed: customTheme[b].pressed,
|
||||
}),
|
||||
),
|
||||
);
|
||||
if (pickerType.type === 'pressed') {
|
||||
setGradientNormalColor1(customTheme[selectedButton].normal);
|
||||
setGradientNormalColor2(c.hex);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setGradientNormalColor1(c.hex);
|
||||
setGradientNormalColor2(customTheme[selectedButton].pressed);
|
||||
}
|
||||
}
|
||||
else if (selectedButton === 'GRADIENT PRESSED') {
|
||||
matrix.forEach((r, i) => r.filter(b => !!b).forEach(b => customTheme[b] = { normal: customTheme[b].normal, pressed: steps[i] }));
|
||||
} else if (selectedButton === 'GRADIENT PRESSED') {
|
||||
matrix.forEach((r, i) =>
|
||||
r
|
||||
.filter((b) => !!b)
|
||||
.forEach(
|
||||
(b) =>
|
||||
(customTheme[b] = {
|
||||
normal: customTheme[b].normal,
|
||||
pressed: steps[i],
|
||||
}),
|
||||
),
|
||||
);
|
||||
if (pickerType.type === 'pressed') {
|
||||
setGradientPressedColor1(customTheme[selectedButton].normal);
|
||||
setGradientPressedColor2(c.hex);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setGradientPressedColor1(c.hex);
|
||||
setGradientPressedColor2(customTheme[selectedButton].pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
customTheme[selectedButton][pickerType.type] = c.hex;
|
||||
}
|
||||
}
|
||||
@@ -203,7 +253,7 @@ const CustomThemePage = () => {
|
||||
|
||||
const saveCurrentColor = () => {
|
||||
const color = selectedColor.hex;
|
||||
if (!color || presetColors.filter(c => c.color === color).length > 0)
|
||||
if (!color || presetColors.filter((c) => c.color === color).length > 0)
|
||||
return;
|
||||
|
||||
const newColors = [...savedColors];
|
||||
@@ -220,11 +270,12 @@ const CustomThemePage = () => {
|
||||
e.stopPropagation();
|
||||
if (selectedButton === buttonName) {
|
||||
setPickerVisible(false);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setLedOverlayTarget(e.target);
|
||||
setSelectedButton(buttonName);
|
||||
setSelectedColor(buttonName === 'ALL' ? '#000000' : customTheme[buttonName].normal);
|
||||
setSelectedColor(
|
||||
buttonName === 'ALL' ? '#000000' : customTheme[buttonName].normal,
|
||||
);
|
||||
setPickerType({ type: 'normal', button: buttonName });
|
||||
setPickerVisible(true);
|
||||
}
|
||||
@@ -232,9 +283,16 @@ const CustomThemePage = () => {
|
||||
|
||||
const submit = async () => {
|
||||
const leds = { ...customTheme };
|
||||
specialButtons.forEach(b => delete leds[b]);
|
||||
const success = await WebApi.setCustomTheme({ hasCustomTheme, customTheme: leds });
|
||||
setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message'));
|
||||
specialButtons.forEach((b) => delete leds[b]);
|
||||
const success = await WebApi.setCustomTheme({
|
||||
hasCustomTheme,
|
||||
customTheme: leds,
|
||||
});
|
||||
setSaveMessage(
|
||||
success
|
||||
? t('Common:saved-success-message')
|
||||
: t('Common:saved-error-message'),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -245,9 +303,15 @@ const CustomThemePage = () => {
|
||||
if (!data.customTheme['ALL'])
|
||||
data.customTheme['ALL'] = { normal: '#000000', pressed: '#000000' };
|
||||
if (!data.customTheme['GRADIENT NORMAL'])
|
||||
data.customTheme['GRADIENT NORMAL'] = { normal: '#00ffff', pressed: '#ff00ff' };
|
||||
data.customTheme['GRADIENT NORMAL'] = {
|
||||
normal: '#00ffff',
|
||||
pressed: '#ff00ff',
|
||||
};
|
||||
if (!data.customTheme['GRADIENT PRESSED'])
|
||||
data.customTheme['GRADIENT PRESSED'] = { normal: '#00ffff', pressed: '#ff00ff' };
|
||||
data.customTheme['GRADIENT PRESSED'] = {
|
||||
normal: '#00ffff',
|
||||
pressed: '#ff00ff',
|
||||
};
|
||||
|
||||
setCustomTheme(data.customTheme);
|
||||
}
|
||||
@@ -255,166 +319,230 @@ const CustomThemePage = () => {
|
||||
fetchData();
|
||||
|
||||
// Hide color picker when anywhere but picker is clicked
|
||||
window.addEventListener('click', (e) => toggleSelectedButton(e, selectedButton));
|
||||
window.addEventListener('click', (e) =>
|
||||
toggleSelectedButton(e, selectedButton),
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pickerVisible)
|
||||
setTimeout(() => setSelectedButton(null), 250); // Delay enough to allow fade animation to finish
|
||||
if (!pickerVisible) setTimeout(() => setSelectedButton(null), 250); // Delay enough to allow fade animation to finish
|
||||
}, [pickerVisible]);
|
||||
|
||||
return <>
|
||||
<Section title={t('CustomTheme:header-text')}>
|
||||
<div>
|
||||
<p>
|
||||
<Trans ns="CustomTheme" i18nKey="sub-header-text">
|
||||
Here you can enable and configure a custom LED theme.
|
||||
The custom theme will be selectable using the Next and Previous Animation shortcuts on your controller.
|
||||
</Trans>
|
||||
</p>
|
||||
{hasCustomTheme &&
|
||||
<>
|
||||
<Stack>
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="d-flex d-none d-md-block">
|
||||
<ul>
|
||||
<Trans ns="CustomTheme" i18nKey="list-text">
|
||||
<li>Click a button to bring up the normal and pressed color selection.</li>
|
||||
<li>Click on the controller background to dismiss the color selection.</li>
|
||||
<li>Right-click a button to preview the button's pressed color.</li>
|
||||
</Trans>
|
||||
</ul>
|
||||
</div>
|
||||
<FormSelect
|
||||
label={t('CustomTheme:led-layout-label')}
|
||||
name="ledLayout"
|
||||
value={ledLayout}
|
||||
onChange={(e) => setLedLayout(e.target.value)}
|
||||
style={{ width: 150 }}
|
||||
>
|
||||
{BUTTON_LAYOUTS.map((o, i) => <option key={`ledLayout-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</FormSelect>
|
||||
</div>
|
||||
<div className="d-flex led-preview-container">
|
||||
<div
|
||||
className={`led-preview led-preview-${BUTTON_LAYOUTS[ledLayout]?.stickLayout}`}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="container-aux">
|
||||
{AUX_BUTTONS.map(buttonName => (
|
||||
<LEDButton
|
||||
key={`led-button-${buttonName}`}
|
||||
className={`${buttonName} ${selectedButton === buttonName ? 'selected' : ''}`}
|
||||
name={BUTTONS[buttonLabelType][buttonName]}
|
||||
buttonColor={customTheme[buttonName]?.normal}
|
||||
buttonPressedColor={customTheme[buttonName]?.pressed}
|
||||
labelUnder={true}
|
||||
onClick={(e) => toggleSelectedButton(e, buttonName)}
|
||||
/>
|
||||
))}
|
||||
return (
|
||||
<>
|
||||
<Section title={t('CustomTheme:header-text')}>
|
||||
<div>
|
||||
<p>
|
||||
<Trans ns="CustomTheme" i18nKey="sub-header-text">
|
||||
Here you can enable and configure a custom LED theme. The custom
|
||||
theme will be selectable using the Next and Previous Animation
|
||||
shortcuts on your controller.
|
||||
</Trans>
|
||||
</p>
|
||||
{hasCustomTheme && (
|
||||
<>
|
||||
<Stack>
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="d-flex d-none d-md-block">
|
||||
<ul>
|
||||
<Trans ns="CustomTheme" i18nKey="list-text">
|
||||
<li>
|
||||
Click a button to bring up the normal and pressed
|
||||
color selection.
|
||||
</li>
|
||||
<li>
|
||||
Click on the controller background to dismiss the
|
||||
color selection.
|
||||
</li>
|
||||
<li>
|
||||
Right-click a button to preview the button's
|
||||
pressed color.
|
||||
</li>
|
||||
</Trans>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="container-main">
|
||||
{MAIN_BUTTONS.map(buttonName => (
|
||||
<LEDButton
|
||||
key={`led-button-${buttonName}`}
|
||||
className={`${buttonName} ${selectedButton === buttonName ? 'selected' : ''}`}
|
||||
name={BUTTONS[buttonLabelType][buttonName]}
|
||||
buttonColor={customTheme[buttonName]?.normal}
|
||||
buttonPressedColor={customTheme[buttonName]?.pressed}
|
||||
labelUnder={false}
|
||||
onClick={(e) => toggleSelectedButton(e, buttonName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
<div className="button-group">
|
||||
<Button onClick={(e) => setModalVisible(true)}>{t('Common:button-clear-all-label')}</Button>
|
||||
<Button onClick={(e) => toggleSelectedButton(e, 'ALL')}>{t('Common:button-set-all-to-color-label')}</Button>
|
||||
<Button onClick={(e) => toggleSelectedButton(e, 'GRADIENT NORMAL')}>{t('Common:button-set-gradient-label')}</Button>
|
||||
<Button onClick={(e) => toggleSelectedButton(e, 'GRADIENT PRESSED')}>{t('Common:button-set-pressed-gradient-label')}</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
<Overlay
|
||||
show={pickerVisible}
|
||||
target={ledOverlayTarget}
|
||||
placement={specialButtons.indexOf(selectedButton) > -1 ? 'top' : 'bottom'}
|
||||
container={this}
|
||||
containerPadding={20}
|
||||
>
|
||||
<Popover onClick={(e) => e.stopPropagation()}>
|
||||
<Container className="led-color-picker">
|
||||
<h6 className="text-center">{specialButtons.indexOf(selectedButton) > -1 ? selectedButton : BUTTONS[buttonLabelType][selectedButton]}</h6>
|
||||
<Row>
|
||||
<Form.Group as={Col}
|
||||
className={`led-color-option ${pickerType?.type === 'normal' ? 'selected' : ''}`}
|
||||
onClick={() => handleLedColorClick('normal')}
|
||||
>
|
||||
<Form.Label>{selectedButton?.startsWith('GRADIENT') ? 'Color 1' : 'Normal'}</Form.Label>
|
||||
<div
|
||||
className={`led-color led-color-normal`}
|
||||
style={{ backgroundColor: customTheme[selectedButton]?.normal }}
|
||||
<FormSelect
|
||||
label={t('CustomTheme:led-layout-label')}
|
||||
name="ledLayout"
|
||||
value={ledLayout}
|
||||
onChange={(e) => setLedLayout(e.target.value)}
|
||||
style={{ width: 150 }}
|
||||
>
|
||||
</div>
|
||||
</Form.Group>
|
||||
<Form.Group as={Col}
|
||||
className={`led-color-option ${pickerType?.type === 'pressed' ? 'selected' : ''}`}
|
||||
onClick={() => handleLedColorClick('pressed')}
|
||||
>
|
||||
<Form.Label>{selectedButton?.startsWith('GRADIENT') ? 'Color 2' : 'Pressed'}</Form.Label>
|
||||
{BUTTON_LAYOUTS.map((o, i) => (
|
||||
<option key={`ledLayout-option-${i}`} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
</div>
|
||||
<div className="d-flex led-preview-container">
|
||||
<div
|
||||
className={`led-color led-color-pressed`}
|
||||
style={{ backgroundColor: customTheme[selectedButton]?.pressed }}
|
||||
></div>
|
||||
</Form.Group>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col>
|
||||
<SketchPicker
|
||||
color={selectedColor}
|
||||
onChange={(c) => handleLedColorChange(c)}
|
||||
disableAlpha={true}
|
||||
presetColors={presetColors}
|
||||
width={180}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="button-group d-flex justify-content-between">
|
||||
<Button size="sm" onClick={() => saveCurrentColor()}>{t('Common:button-save-color-label')}</Button>
|
||||
<Button size="sm" onClick={() => deleteCurrentColor()}>{t('Common:button-delete-color-label')}</Button>
|
||||
className={`led-preview led-preview-${BUTTON_LAYOUTS[ledLayout]?.stickLayout}`}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<div className="container-aux">
|
||||
{AUX_BUTTONS.map((buttonName) => (
|
||||
<LEDButton
|
||||
key={`led-button-${buttonName}`}
|
||||
className={`${buttonName} ${
|
||||
selectedButton === buttonName ? 'selected' : ''
|
||||
}`}
|
||||
name={BUTTONS[buttonLabelType][buttonName]}
|
||||
buttonColor={customTheme[buttonName]?.normal}
|
||||
buttonPressedColor={customTheme[buttonName]?.pressed}
|
||||
labelUnder={true}
|
||||
onClick={(e) => toggleSelectedButton(e, buttonName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="container-main">
|
||||
{MAIN_BUTTONS.map((buttonName) => (
|
||||
<LEDButton
|
||||
key={`led-button-${buttonName}`}
|
||||
className={`${buttonName} ${
|
||||
selectedButton === buttonName ? 'selected' : ''
|
||||
}`}
|
||||
name={BUTTONS[buttonLabelType][buttonName]}
|
||||
buttonColor={customTheme[buttonName]?.normal}
|
||||
buttonPressedColor={customTheme[buttonName]?.pressed}
|
||||
labelUnder={false}
|
||||
onClick={(e) => toggleSelectedButton(e, buttonName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
<div className="button-group">
|
||||
<Button onClick={(e) => setModalVisible(true)}>
|
||||
{t('Common:button-clear-all-label')}
|
||||
</Button>
|
||||
<Button onClick={(e) => toggleSelectedButton(e, 'ALL')}>
|
||||
{t('Common:button-set-all-to-color-label')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => toggleSelectedButton(e, 'GRADIENT NORMAL')}
|
||||
>
|
||||
{t('Common:button-set-gradient-label')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => toggleSelectedButton(e, 'GRADIENT PRESSED')}
|
||||
>
|
||||
{t('Common:button-set-pressed-gradient-label')}
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</Popover>
|
||||
</Overlay>
|
||||
</>
|
||||
)}
|
||||
<Overlay
|
||||
show={pickerVisible}
|
||||
target={ledOverlayTarget}
|
||||
placement={
|
||||
specialButtons.indexOf(selectedButton) > -1 ? 'top' : 'bottom'
|
||||
}
|
||||
container={this}
|
||||
containerPadding={20}
|
||||
>
|
||||
<Popover onClick={(e) => e.stopPropagation()}>
|
||||
<Container className="led-color-picker">
|
||||
<h6 className="text-center">
|
||||
{specialButtons.indexOf(selectedButton) > -1
|
||||
? selectedButton
|
||||
: BUTTONS[buttonLabelType][selectedButton]}
|
||||
</h6>
|
||||
<Row>
|
||||
<Form.Group
|
||||
as={Col}
|
||||
className={`led-color-option ${
|
||||
pickerType?.type === 'normal' ? 'selected' : ''
|
||||
}`}
|
||||
onClick={() => handleLedColorClick('normal')}
|
||||
>
|
||||
<Form.Label>
|
||||
{selectedButton?.startsWith('GRADIENT')
|
||||
? 'Color 1'
|
||||
: 'Normal'}
|
||||
</Form.Label>
|
||||
<div
|
||||
className={`led-color led-color-normal`}
|
||||
style={{
|
||||
backgroundColor: customTheme[selectedButton]?.normal,
|
||||
}}
|
||||
></div>
|
||||
</Form.Group>
|
||||
<Form.Group
|
||||
as={Col}
|
||||
className={`led-color-option ${
|
||||
pickerType?.type === 'pressed' ? 'selected' : ''
|
||||
}`}
|
||||
onClick={() => handleLedColorClick('pressed')}
|
||||
>
|
||||
<Form.Label>
|
||||
{selectedButton?.startsWith('GRADIENT')
|
||||
? 'Color 2'
|
||||
: 'Pressed'}
|
||||
</Form.Label>
|
||||
<div
|
||||
className={`led-color led-color-pressed`}
|
||||
style={{
|
||||
backgroundColor: customTheme[selectedButton]?.pressed,
|
||||
}}
|
||||
></div>
|
||||
</Form.Group>
|
||||
</Row>
|
||||
<Row className="mb-2">
|
||||
<Col>
|
||||
<SketchPicker
|
||||
color={selectedColor}
|
||||
onChange={(c) => handleLedColorChange(c)}
|
||||
disableAlpha={true}
|
||||
presetColors={presetColors}
|
||||
width={180}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="button-group d-flex justify-content-between">
|
||||
<Button size="sm" onClick={() => saveCurrentColor()}>
|
||||
{t('Common:button-save-color-label')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => deleteCurrentColor()}>
|
||||
{t('Common:button-delete-color-label')}
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</Popover>
|
||||
</Overlay>
|
||||
</div>
|
||||
<FormCheck
|
||||
label={t('CustomTheme:has-custom-theme-label')}
|
||||
type="switch"
|
||||
id="hasCustomTheme"
|
||||
reverse="true"
|
||||
error={undefined}
|
||||
isInvalid={false}
|
||||
checked={hasCustomTheme}
|
||||
onChange={(e) => toggleCustomTheme(e)}
|
||||
/>
|
||||
</Section>
|
||||
<div>
|
||||
<Button onClick={submit}>{t('Common:button-save-label')}</Button>
|
||||
{saveMessage ? <span className="alert">{saveMessage}</span> : null}
|
||||
</div>
|
||||
<FormCheck
|
||||
label={t('CustomTheme:has-custom-theme-label')}
|
||||
type="switch"
|
||||
id="hasCustomTheme"
|
||||
reverse="true"
|
||||
error={undefined}
|
||||
isInvalid={false}
|
||||
checked={hasCustomTheme}
|
||||
onChange={(e) => toggleCustomTheme(e)}
|
||||
/>
|
||||
</Section>
|
||||
<div>
|
||||
<Button onClick={submit}>{t('Common:button-save-label')}</Button>
|
||||
{saveMessage ? <span className="alert">{saveMessage}</span> : null}
|
||||
</div>
|
||||
<Modal show={modalVisible} onHide={() => setModalVisible(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('CustomTheme:modal-title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{t('CustomTheme:modal-body')}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="danger" onClick={() => setModalVisible(false)}>{t('CustomTheme:modal-yes')}</Button>
|
||||
<Button variant="success" onClick={() => confirmClearAll()}>{t('CustomTheme:modal-no')}</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>;
|
||||
<Modal show={modalVisible} onHide={() => setModalVisible(false)}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('CustomTheme:modal-title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{t('CustomTheme:modal-body')}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="danger" onClick={() => setModalVisible(false)}>
|
||||
{t('CustomTheme:modal-yes')}
|
||||
</Button>
|
||||
<Button variant="success" onClick={() => confirmClearAll()}>
|
||||
{t('CustomTheme:modal-no')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomThemePage;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #DEDEDE;
|
||||
background-color: #dedede;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
margin: 0 auto 10px;
|
||||
border: 1px solid black;
|
||||
|
||||
&-normal, &-pressed {
|
||||
&-normal,
|
||||
&-pressed {
|
||||
width: 40px !important;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +58,7 @@
|
||||
|
||||
$preview-height: 400px;
|
||||
$preview-width: calc($preview-height * 2);
|
||||
$preview-padding: calc($preview-height / 20);;
|
||||
$preview-padding: calc($preview-height / 20);
|
||||
$button-size-multiplier: 3;
|
||||
$button-12mm: 12px;
|
||||
$button-24mm: 24px;
|
||||
@@ -92,7 +93,7 @@ $button-keycap: 19px;
|
||||
}
|
||||
|
||||
.led-preview {
|
||||
$background-color: #AAA;
|
||||
$background-color: #aaa;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -105,7 +106,8 @@ $button-keycap: 19px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.container-main, .container-aux {
|
||||
.container-main,
|
||||
.container-aux {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -115,16 +117,20 @@ $button-keycap: 19px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.led-button { transform: translate(-50%, 0); position: absolute; }
|
||||
.led-button {
|
||||
transform: translate(-50%, 0);
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.container-aux {
|
||||
height: $button-12mm * 5;
|
||||
margin-bottom: $button-12mm * 2;
|
||||
|
||||
.led-button { margin: $button-12mm; }
|
||||
.led-button {
|
||||
margin: $button-12mm;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.led-button {
|
||||
@@ -132,7 +138,12 @@ $button-keycap: 19px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
text-shadow: #000 0 -1px 0.5px, #000 0 1px 0.5px, #000 1px 0 0.5px, #000 -1px 0 0.5px, 0 0 2px #000;
|
||||
text-shadow:
|
||||
#000 0 -1px 0.5px,
|
||||
#000 0 1px 0.5px,
|
||||
#000 1px 0 0.5px,
|
||||
#000 -1px 0 0.5px,
|
||||
0 0 2px #000;
|
||||
box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -141,12 +152,30 @@ $button-keycap: 19px;
|
||||
box-shadow: 0px 0px 2px 2px rgba(255, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.S1 { @include arcade-button($button-12mm, true); left: $button-12mm * 3; }
|
||||
&.S2 { @include arcade-button($button-12mm, true); left: $button-12mm * 8; }
|
||||
&.A1 { @include arcade-button($button-12mm, true); left: $button-12mm * 13; }
|
||||
&.A2 { @include arcade-button($button-12mm, true); left: $button-12mm * 18; }
|
||||
&.L3 { @include arcade-button($button-12mm, true); left: $button-12mm * 23; }
|
||||
&.R3 { @include arcade-button($button-12mm, true); left: $button-12mm * 28; }
|
||||
&.S1 {
|
||||
@include arcade-button($button-12mm, true);
|
||||
left: $button-12mm * 3;
|
||||
}
|
||||
&.S2 {
|
||||
@include arcade-button($button-12mm, true);
|
||||
left: $button-12mm * 8;
|
||||
}
|
||||
&.A1 {
|
||||
@include arcade-button($button-12mm, true);
|
||||
left: $button-12mm * 13;
|
||||
}
|
||||
&.A2 {
|
||||
@include arcade-button($button-12mm, true);
|
||||
left: $button-12mm * 18;
|
||||
}
|
||||
&.L3 {
|
||||
@include arcade-button($button-12mm, true);
|
||||
left: $button-12mm * 23;
|
||||
}
|
||||
&.R3 {
|
||||
@include arcade-button($button-12mm, true);
|
||||
left: $button-12mm * 28;
|
||||
}
|
||||
|
||||
.button-label {
|
||||
font-size: 0.85rem;
|
||||
@@ -160,56 +189,209 @@ $button-keycap: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
.button-30mm { @include arcade-button($button-30mm, true); }
|
||||
.button-24mm { @include arcade-button($button-24mm, true); }
|
||||
.button-keycap { @include arcade-button($button-keycap, false); }
|
||||
.button-joystick { @include arcade-button($button-joystick, true); }
|
||||
.button-30mm {
|
||||
@include arcade-button($button-30mm, true);
|
||||
}
|
||||
.button-24mm {
|
||||
@include arcade-button($button-24mm, true);
|
||||
}
|
||||
.button-keycap {
|
||||
@include arcade-button($button-keycap, false);
|
||||
}
|
||||
.button-joystick {
|
||||
@include arcade-button($button-joystick, true);
|
||||
}
|
||||
|
||||
.led-preview-keyboard {
|
||||
.Up { @include arcade-button($button-keycap, false); left: calc(50% - #{$button-24mm * 8.25}); top: $button-24mm * 1.25; }
|
||||
.Left { @include arcade-button($button-keycap, false); left: calc(50% - #{$button-24mm * 11}); top: $button-24mm * 4; }
|
||||
.Down { @include arcade-button($button-keycap, false); left: calc(50% - #{$button-24mm * 8.25}); top: $button-24mm * 4; }
|
||||
.Right { @include arcade-button($button-keycap, false); left: calc(50% - #{$button-24mm * 5.5}); top: $button-24mm * 4; }
|
||||
.Up {
|
||||
@include arcade-button($button-keycap, false);
|
||||
left: calc(50% - #{$button-24mm * 8.25});
|
||||
top: $button-24mm * 1.25;
|
||||
}
|
||||
.Left {
|
||||
@include arcade-button($button-keycap, false);
|
||||
left: calc(50% - #{$button-24mm * 11});
|
||||
top: $button-24mm * 4;
|
||||
}
|
||||
.Down {
|
||||
@include arcade-button($button-keycap, false);
|
||||
left: calc(50% - #{$button-24mm * 8.25});
|
||||
top: $button-24mm * 4;
|
||||
}
|
||||
.Right {
|
||||
@include arcade-button($button-keycap, false);
|
||||
left: calc(50% - #{$button-24mm * 5.5});
|
||||
top: $button-24mm * 4;
|
||||
}
|
||||
|
||||
.B3 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 0.5}); top: $button-30mm; }
|
||||
.B4 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 3.75}); top: 0; }
|
||||
.R1 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 7}); top: 0; }
|
||||
.L1 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 10.25}); top: 0; }
|
||||
.B1 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 0.25}); top: $button-30mm * 4.25; }
|
||||
.B2 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 3.5}); top: $button-30mm * 3.25; }
|
||||
.R2 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 6.75}); top: $button-30mm * 3.25; }
|
||||
.L2 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 10}); top: $button-30mm * 3.25; }
|
||||
.B3 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 0.5});
|
||||
top: $button-30mm;
|
||||
}
|
||||
.B4 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 3.75});
|
||||
top: 0;
|
||||
}
|
||||
.R1 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 7});
|
||||
top: 0;
|
||||
}
|
||||
.L1 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 10.25});
|
||||
top: 0;
|
||||
}
|
||||
.B1 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 0.25});
|
||||
top: $button-30mm * 4.25;
|
||||
}
|
||||
.B2 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 3.5});
|
||||
top: $button-30mm * 3.25;
|
||||
}
|
||||
.R2 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 6.75});
|
||||
top: $button-30mm * 3.25;
|
||||
}
|
||||
.L2 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 10});
|
||||
top: $button-30mm * 3.25;
|
||||
}
|
||||
}
|
||||
|
||||
.led-preview-standard {
|
||||
.Up { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 8.5}); top: 0; }
|
||||
.Left { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 11.5}); top: $button-24mm * 3; }
|
||||
.Down { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 8.5}); top: $button-24mm * 6; }
|
||||
.Right { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 5.5}); top: $button-24mm * 3; }
|
||||
.Up {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 8.5});
|
||||
top: 0;
|
||||
}
|
||||
.Left {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 11.5});
|
||||
top: $button-24mm * 3;
|
||||
}
|
||||
.Down {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 8.5});
|
||||
top: $button-24mm * 6;
|
||||
}
|
||||
.Right {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 5.5});
|
||||
top: $button-24mm * 3;
|
||||
}
|
||||
|
||||
.B3 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 0.5}); top: $button-30mm; }
|
||||
.B4 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 3.75}); top: 0; }
|
||||
.R1 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 7}); top: 0; }
|
||||
.L1 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 10.25}); top: 0; }
|
||||
.B1 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 0.25}); top: $button-30mm * 4.25; }
|
||||
.B2 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 3.5}); top: $button-30mm * 3.25; }
|
||||
.R2 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 6.75}); top: $button-30mm * 3.25; }
|
||||
.L2 { @include arcade-button($button-30mm, true); left: calc(50% + #{$button-30mm * 10}); top: $button-30mm * 3.25; }
|
||||
.B3 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 0.5});
|
||||
top: $button-30mm;
|
||||
}
|
||||
.B4 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 3.75});
|
||||
top: 0;
|
||||
}
|
||||
.R1 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 7});
|
||||
top: 0;
|
||||
}
|
||||
.L1 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 10.25});
|
||||
top: 0;
|
||||
}
|
||||
.B1 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 0.25});
|
||||
top: $button-30mm * 4.25;
|
||||
}
|
||||
.B2 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 3.5});
|
||||
top: $button-30mm * 3.25;
|
||||
}
|
||||
.R2 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 6.75});
|
||||
top: $button-30mm * 3.25;
|
||||
}
|
||||
.L2 {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: calc(50% + #{$button-30mm * 10});
|
||||
top: $button-30mm * 3.25;
|
||||
}
|
||||
}
|
||||
|
||||
.led-preview-stickless {
|
||||
.Up { @include arcade-button($button-30mm, true); left: 50%; right: 50%; top: calc(50% + #{$button-24mm * 1.5}); }
|
||||
.Up {
|
||||
@include arcade-button($button-30mm, true);
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
top: calc(50% + #{$button-24mm * 1.5});
|
||||
}
|
||||
|
||||
.Left { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 7.75}); top: $button-24mm; }
|
||||
.Down { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 4.5}); top: $button-24mm; }
|
||||
.Right { @include arcade-button($button-24mm, true); left: calc(50% - #{$button-24mm * 1.5}); top: $button-24mm * 2.5; }
|
||||
.Left {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 7.75});
|
||||
top: $button-24mm;
|
||||
}
|
||||
.Down {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 4.5});
|
||||
top: $button-24mm;
|
||||
}
|
||||
.Right {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% - #{$button-24mm * 1.5});
|
||||
top: $button-24mm * 2.5;
|
||||
}
|
||||
|
||||
.B3 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 1.5}); top: $button-24mm; }
|
||||
.B4 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 4.75}); top: 0; }
|
||||
.R1 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 8}); top: 0; }
|
||||
.L1 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 11.25}); top: 0; }
|
||||
.B1 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 1.25}); top: $button-24mm * 4.25; }
|
||||
.B2 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 4.5}); top: $button-24mm * 3.25; }
|
||||
.R2 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 7.75}); top: $button-24mm * 3.25; }
|
||||
.L2 { @include arcade-button($button-24mm, true); left: calc(50% + #{$button-24mm * 11}); top: $button-24mm * 3.25; }
|
||||
.B3 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 1.5});
|
||||
top: $button-24mm;
|
||||
}
|
||||
.B4 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 4.75});
|
||||
top: 0;
|
||||
}
|
||||
.R1 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 8});
|
||||
top: 0;
|
||||
}
|
||||
.L1 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 11.25});
|
||||
top: 0;
|
||||
}
|
||||
.B1 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 1.25});
|
||||
top: $button-24mm * 4.25;
|
||||
}
|
||||
.B2 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 4.5});
|
||||
top: $button-24mm * 3.25;
|
||||
}
|
||||
.R2 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 7.75});
|
||||
top: $button-24mm * 3.25;
|
||||
}
|
||||
.L2 {
|
||||
@include arcade-button($button-24mm, true);
|
||||
left: calc(50% + #{$button-24mm * 11});
|
||||
top: $button-24mm * 3.25;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,14 +8,16 @@ import Section from '../Components/Section';
|
||||
|
||||
import WebApi from '../Services/WebApi';
|
||||
|
||||
const percentage = (x, y) => (x / y * 100).toFixed(2)
|
||||
const toKB = (x) => parseFloat((x / 1024).toFixed(2))
|
||||
const percentage = (x, y) => ((x / y) * 100).toFixed(2);
|
||||
const toKB = (x) => parseFloat((x / 1024).toFixed(2));
|
||||
let loading = true;
|
||||
|
||||
export default function HomePage() {
|
||||
const [latestVersion, setLatestVersion] = useState('');
|
||||
const [latestTag, setLatestTag] = useState('');
|
||||
const [currentVersion, setCurrentVersion] = useState(import.meta.env.VITE_CURRENT_VERSION);
|
||||
const [currentVersion, setCurrentVersion] = useState(
|
||||
import.meta.env.VITE_CURRENT_VERSION,
|
||||
);
|
||||
const [memoryReport, setMemoryReport] = useState(null);
|
||||
|
||||
const { t } = useTranslation('');
|
||||
@@ -23,27 +25,31 @@ export default function HomePage() {
|
||||
const { setLoading } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
WebApi.getFirmwareVersion(setLoading).then(response => {
|
||||
setCurrentVersion(response.version);
|
||||
})
|
||||
WebApi.getFirmwareVersion(setLoading)
|
||||
.then((response) => {
|
||||
setCurrentVersion(response.version);
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
WebApi.getMemoryReport(setLoading).then(response => {
|
||||
const unit = 1024;
|
||||
const { totalFlash, usedFlash, staticAllocs, totalHeap, usedHeap } = response;
|
||||
setMemoryReport({
|
||||
totalFlash: toKB(totalFlash),
|
||||
usedFlash: toKB(usedFlash),
|
||||
staticAllocs: toKB(staticAllocs),
|
||||
totalHeap: toKB(totalHeap),
|
||||
usedHeap: toKB(usedHeap),
|
||||
percentageFlash: percentage(usedFlash, totalFlash),
|
||||
percentageHeap: percentage(usedHeap, totalHeap)
|
||||
});
|
||||
})
|
||||
WebApi.getMemoryReport(setLoading)
|
||||
.then((response) => {
|
||||
const unit = 1024;
|
||||
const { totalFlash, usedFlash, staticAllocs, totalHeap, usedHeap } =
|
||||
response;
|
||||
setMemoryReport({
|
||||
totalFlash: toKB(totalFlash),
|
||||
usedFlash: toKB(usedFlash),
|
||||
staticAllocs: toKB(staticAllocs),
|
||||
totalHeap: toKB(totalHeap),
|
||||
usedHeap: toKB(usedHeap),
|
||||
percentageFlash: percentage(usedFlash, totalFlash),
|
||||
percentageHeap: percentage(usedHeap, totalHeap),
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
axios.get('https://api.github.com/repos/OpenStickCommunity/GP2040-CE/releases')
|
||||
axios
|
||||
.get('https://api.github.com/repos/OpenStickCommunity/GP2040-CE/releases')
|
||||
.then((response) => {
|
||||
// Filter out pre-releases
|
||||
response.data = response.data.filter((release) => !release.prerelease);
|
||||
@@ -61,27 +67,40 @@ export default function HomePage() {
|
||||
<p>{t('HomePage:sub-header-text')}</p>
|
||||
<Section title={t('HomePage:system-stats-header-text')}>
|
||||
<div>
|
||||
<div><strong>{t('HomePage:version-text')}</strong></div>
|
||||
<div>
|
||||
<strong>{t('HomePage:version-text')}</strong>
|
||||
</div>
|
||||
<div>{t('HomePage:current-text', { version: currentVersion })}</div>
|
||||
<div>{t('HomePage:latest-text', { version: latestVersion })}</div>
|
||||
{(latestVersion && currentVersion !== latestVersion) &&
|
||||
{latestVersion && currentVersion !== latestVersion && (
|
||||
<div className="mt-3 mb-3">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={`https://github.com/OpenStickCommunity/GP2040-CE/releases/tag/${latestTag}`}
|
||||
className="btn btn-primary"
|
||||
>{t('HomePage:get-update-text')}</a>
|
||||
>
|
||||
{t('HomePage:get-update-text')}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
{memoryReport &&
|
||||
)}
|
||||
{memoryReport && (
|
||||
<div>
|
||||
<strong>{t('HomePage:memory-header-text')}</strong>
|
||||
<div>{t('HomePage:memory-flash-text')}: {memoryReport.usedFlash} / {memoryReport.totalFlash} ({memoryReport.percentageFlash}%)</div>
|
||||
<div>{t('HomePage:memory-heap-text')}: {memoryReport.usedHeap} / {memoryReport.totalHeap} ({memoryReport.percentageHeap}%)</div>
|
||||
<div>{t('HomePage:memory-static-allocations-text')}: {memoryReport.staticAllocs}</div>
|
||||
<div>
|
||||
{t('HomePage:memory-flash-text')}: {memoryReport.usedFlash} /{' '}
|
||||
{memoryReport.totalFlash} ({memoryReport.percentageFlash}%)
|
||||
</div>
|
||||
<div>
|
||||
{t('HomePage:memory-heap-text')}: {memoryReport.usedHeap} /{' '}
|
||||
{memoryReport.totalHeap} ({memoryReport.percentageHeap}%)
|
||||
</div>
|
||||
<div>
|
||||
{t('HomePage:memory-static-allocations-text')}:{' '}
|
||||
{memoryReport.staticAllocs}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
|
||||
@@ -43,13 +43,17 @@ export default function KeyboardMappingPage() {
|
||||
setKeyMappings(mappings);
|
||||
setValidated(true);
|
||||
|
||||
if (Object.keys(mappings).filter(p => !!mappings[p].error).length) {
|
||||
if (Object.keys(mappings).filter((p) => !!mappings[p].error).length) {
|
||||
setSaveMessage(t('Common:errors.validation-error'));
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await WebApi.setKeyMappings(mappings);
|
||||
setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message'));
|
||||
setSaveMessage(
|
||||
success
|
||||
? t('Common:saved-success-message')
|
||||
: t('Common:saved-error-message'),
|
||||
);
|
||||
};
|
||||
|
||||
const getKeyMappingForButton = (button) => keyMappings[button];
|
||||
@@ -58,10 +62,12 @@ export default function KeyboardMappingPage() {
|
||||
<Section title={t('KeyboardMapping:header-text')}>
|
||||
<Form noValidate validated={validated} onSubmit={handleSubmit}>
|
||||
<p>{t('KeyboardMapping:sub-header-text')}</p>
|
||||
<KeyboardMapper buttonLabels={buttonLabels}
|
||||
<KeyboardMapper
|
||||
buttonLabels={buttonLabels}
|
||||
handleKeyChange={handleKeyChange}
|
||||
validated={validated}
|
||||
getKeyMappingForButton={getKeyMappingForButton} />
|
||||
getKeyMappingForButton={getKeyMappingForButton}
|
||||
/>
|
||||
<Button type="submit">{t('Common:button-save-label')}</Button>
|
||||
{saveMessage ? <span className="alert">{saveMessage}</span> : null}
|
||||
</Form>
|
||||
|
||||
@@ -59,65 +59,133 @@ const defaultValue = {
|
||||
};
|
||||
|
||||
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'),
|
||||
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'),
|
||||
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),
|
||||
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'),
|
||||
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'),
|
||||
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, swapTpShareLabels) => {
|
||||
return orderBy(
|
||||
Object
|
||||
.keys(BUTTONS[buttonLabels])
|
||||
.filter(p => p !== 'label' && p !== 'value')
|
||||
.filter(p => excludeNulls ? map[p] > -1 : true)
|
||||
.map(p => {
|
||||
Object.keys(BUTTONS[buttonLabels])
|
||||
.filter((p) => p !== 'label' && p !== 'value')
|
||||
.filter((p) => (excludeNulls ? map[p] > -1 : true))
|
||||
.map((p) => {
|
||||
let label = BUTTONS[buttonLabels][p];
|
||||
if (p === "S1" && swapTpShareLabels && buttonLabels === "ps4") {
|
||||
label = BUTTONS[buttonLabels]["A2"];
|
||||
if (p === 'S1' && swapTpShareLabels && buttonLabels === 'ps4') {
|
||||
label = BUTTONS[buttonLabels]['A2'];
|
||||
}
|
||||
if (p === "A2" && swapTpShareLabels && buttonLabels === "ps4") {
|
||||
label = BUTTONS[buttonLabels]["S1"];
|
||||
if (p === 'A2' && swapTpShareLabels && buttonLabels === 'ps4') {
|
||||
label = BUTTONS[buttonLabels]['S1'];
|
||||
}
|
||||
return ({ id: p, label: BUTTONS[buttonLabels][p], value: map[p] });
|
||||
return { id: p, label: BUTTONS[buttonLabels][p], value: map[p] };
|
||||
}),
|
||||
"value"
|
||||
'value',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getLedMap = (buttonLabels, ledButtons, excludeNulls) => {
|
||||
if (!ledButtons)
|
||||
return;
|
||||
if (!ledButtons) return;
|
||||
|
||||
const map = Object
|
||||
.keys(BUTTONS[buttonLabels])
|
||||
.filter(p => p !== 'label' && p !== 'value')
|
||||
.filter(p => excludeNulls ? ledButtons[p].value > -1 : true)
|
||||
.reduce((p, n) => { p[n] = null; return p }, {});
|
||||
const map = Object.keys(BUTTONS[buttonLabels])
|
||||
.filter((p) => p !== 'label' && p !== 'value')
|
||||
.filter((p) => (excludeNulls ? ledButtons[p].value > -1 : true))
|
||||
.reduce((p, n) => {
|
||||
p[n] = null;
|
||||
return p;
|
||||
}, {});
|
||||
|
||||
for (let i = 0; i < ledButtons.length; i++)
|
||||
map[ledButtons[i].id] = i;
|
||||
for (let i = 0; i < ledButtons.length; i++) map[ledButtons[i].id] = i;
|
||||
|
||||
return map;
|
||||
}
|
||||
};
|
||||
|
||||
const FormContext = ({
|
||||
buttonLabels, ledButtonMap, ledFormat, pledColor, pledType, swapTpShareLabels,
|
||||
pledPin1, pledPin2, pledPin3, pledPin4,
|
||||
pledIndex1, pledIndex2, pledIndex3, pledIndex4,
|
||||
setDataSources
|
||||
buttonLabels,
|
||||
ledButtonMap,
|
||||
ledFormat,
|
||||
pledColor,
|
||||
pledType,
|
||||
swapTpShareLabels,
|
||||
pledPin1,
|
||||
pledPin2,
|
||||
pledPin3,
|
||||
pledPin4,
|
||||
pledIndex1,
|
||||
pledIndex2,
|
||||
pledIndex3,
|
||||
pledIndex4,
|
||||
setDataSources,
|
||||
}) => {
|
||||
const { setFieldValue, setValues } = useFormikContext();
|
||||
const { setLoading } = useContext(AppContext);
|
||||
@@ -129,11 +197,9 @@ const FormContext = ({
|
||||
let available = {};
|
||||
let assigned = {};
|
||||
|
||||
Object.keys(data.ledButtonMap).forEach(p => {
|
||||
if (data.ledButtonMap[p] === null)
|
||||
available[p] = data.ledButtonMap[p];
|
||||
else
|
||||
assigned[p] = data.ledButtonMap[p];
|
||||
Object.keys(data.ledButtonMap).forEach((p) => {
|
||||
if (data.ledButtonMap[p] === null) available[p] = data.ledButtonMap[p];
|
||||
else assigned[p] = data.ledButtonMap[p];
|
||||
});
|
||||
|
||||
const dataSources = [
|
||||
@@ -154,8 +220,7 @@ const FormContext = ({
|
||||
}, [buttonLabels, swapTpShareLabels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!ledFormat)
|
||||
setFieldValue('ledFormat', parseInt(ledFormat));
|
||||
if (!!ledFormat) setFieldValue('ledFormat', parseInt(ledFormat));
|
||||
}, [ledFormat, setFieldValue]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -163,40 +228,31 @@ const FormContext = ({
|
||||
}, [ledButtonMap, setFieldValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!pledPin1)
|
||||
setFieldValue('pledPin1', parseInt(pledPin1));
|
||||
if (!!pledPin1) setFieldValue('pledPin1', parseInt(pledPin1));
|
||||
}, [pledPin1, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledPin2)
|
||||
setFieldValue('pledPin2', parseInt(pledPin2));
|
||||
if (!!pledPin2) setFieldValue('pledPin2', parseInt(pledPin2));
|
||||
}, [pledPin2, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledPin3)
|
||||
setFieldValue('pledPin3', parseInt(pledPin3));
|
||||
if (!!pledPin3) setFieldValue('pledPin3', parseInt(pledPin3));
|
||||
}, [pledPin3, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledPin4)
|
||||
setFieldValue('pledPin4', parseInt(pledPin4));
|
||||
if (!!pledPin4) setFieldValue('pledPin4', parseInt(pledPin4));
|
||||
}, [pledPin4, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledIndex1)
|
||||
setFieldValue('pledIndex1', parseInt(pledIndex1));
|
||||
if (!!pledIndex1) setFieldValue('pledIndex1', parseInt(pledIndex1));
|
||||
}, [pledIndex1, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledIndex2)
|
||||
setFieldValue('pledIndex2', parseInt(pledIndex2));
|
||||
if (!!pledIndex2) setFieldValue('pledIndex2', parseInt(pledIndex2));
|
||||
}, [pledIndex2, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledIndex3)
|
||||
setFieldValue('pledIndex3', parseInt(pledIndex3));
|
||||
if (!!pledIndex3) setFieldValue('pledIndex3', parseInt(pledIndex3));
|
||||
}, [pledIndex3, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledIndex4)
|
||||
setFieldValue('pledIndex4', parseInt(pledIndex4));
|
||||
if (!!pledIndex4) setFieldValue('pledIndex4', parseInt(pledIndex4));
|
||||
}, [pledIndex4, setFieldValue]);
|
||||
useEffect(() => {
|
||||
if (!!pledColor)
|
||||
setFieldValue('pledColor', pledColor);
|
||||
if (!!pledColor) setFieldValue('pledColor', pledColor);
|
||||
}, [pledColor, setFieldValue]);
|
||||
|
||||
return null;
|
||||
@@ -221,12 +277,15 @@ export default function LEDConfigPage() {
|
||||
p[1] = t(`LedConfig:pled-index-label`, { index: n });
|
||||
});
|
||||
|
||||
|
||||
const ledOrderChanged = (ledOrderArrays, ledsPerButton) => {
|
||||
if (ledOrderArrays.length === 2) {
|
||||
setLedButtonMap(getLedMap(buttonLabelType, ledOrderArrays[1]));
|
||||
setRgbLedStartIndex(ledOrderArrays[1].length * (ledsPerButton || 0));
|
||||
console.log('new start index: ', ledOrderArrays[1].length * (ledsPerButton || 0), ledOrderArrays);
|
||||
console.log(
|
||||
'new start index: ',
|
||||
ledOrderArrays[1].length * (ledsPerButton || 0),
|
||||
ledOrderArrays,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -254,14 +313,16 @@ export default function LEDConfigPage() {
|
||||
const onSuccess = async (values) => {
|
||||
const data = { ...values };
|
||||
data.pledType = parseInt(values.pledType);
|
||||
if (data.pledColor)
|
||||
data.pledColor = hexToInt(values.pledColor);
|
||||
if (data.pledColor) data.pledColor = hexToInt(values.pledColor);
|
||||
|
||||
const success = await WebApi.setLedOptions(data);
|
||||
if (success)
|
||||
updateUsedPins();
|
||||
if (success) updateUsedPins();
|
||||
|
||||
setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message'));
|
||||
setSaveMessage(
|
||||
success
|
||||
? t('Common:saved-success-message')
|
||||
: t('Common:saved-error-message'),
|
||||
);
|
||||
};
|
||||
|
||||
const onSubmit = (e, handleSubmit, setValues, values) => {
|
||||
@@ -296,7 +357,11 @@ export default function LEDConfigPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik validationSchema={schema} onSubmit={onSuccess} initialValues={defaultValue}>
|
||||
<Formik
|
||||
validationSchema={schema}
|
||||
onSubmit={onSuccess}
|
||||
initialValues={defaultValue}
|
||||
>
|
||||
{({
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
@@ -305,10 +370,14 @@ export default function LEDConfigPage() {
|
||||
values,
|
||||
errors,
|
||||
}) => (
|
||||
<Form noValidate onSubmit={(e) => onSubmit(e, handleSubmit, setValues, values)}>
|
||||
<Form
|
||||
noValidate
|
||||
onSubmit={(e) => onSubmit(e, handleSubmit, setValues, values)}
|
||||
>
|
||||
<Section title={t('LedConfig:rgb.header-text')}>
|
||||
<Row>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
label={t('LedConfig:rgb.data-pin-label')}
|
||||
name="dataPin"
|
||||
className="form-control-sm"
|
||||
@@ -330,7 +399,11 @@ export default function LEDConfigPage() {
|
||||
isInvalid={errors.ledFormat}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{LED_FORMATS.map((o, i) => <option key={`ledFormat-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
{LED_FORMATS.map((o, i) => (
|
||||
<option key={`ledFormat-option-${i}`} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
<FormSelect
|
||||
label={t('LedConfig:rgb.led-layout-label')}
|
||||
@@ -342,11 +415,16 @@ export default function LEDConfigPage() {
|
||||
isInvalid={errors.ledLayout}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{BUTTON_LAYOUTS.map((o, i) => <option key={`ledLayout-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
{BUTTON_LAYOUTS.map((o, i) => (
|
||||
<option key={`ledLayout-option-${i}`} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
</Row>
|
||||
<Row>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
label={t('LedConfig:rgb.leds-per-button-label')}
|
||||
name="ledsPerButton"
|
||||
className="form-control-sm"
|
||||
@@ -357,7 +435,8 @@ export default function LEDConfigPage() {
|
||||
onChange={(e) => ledsPerButtonChanged(e, handleChange)}
|
||||
min={1}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
label={t('LedConfig:rgb.led-brightness-maximum-label')}
|
||||
name="brightnessMaximum"
|
||||
className="form-control-sm"
|
||||
@@ -369,7 +448,8 @@ export default function LEDConfigPage() {
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
label={t('LedConfig:rgb.led-brightness-steps-label')}
|
||||
name="brightnessSteps"
|
||||
className="form-control-sm"
|
||||
@@ -396,11 +476,18 @@ export default function LEDConfigPage() {
|
||||
isInvalid={errors.pledType}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="-1" defaultValue={true}>{t('LedConfig:player.pled-type-off')}</option>
|
||||
<option value="0">{t('LedConfig:player.pled-type-pwm')}</option>
|
||||
<option value="1">{t('LedConfig:player.pled-type-rgb')}</option>
|
||||
<option value="-1" defaultValue={true}>
|
||||
{t('LedConfig:player.pled-type-off')}
|
||||
</option>
|
||||
<option value="0">
|
||||
{t('LedConfig:player.pled-type-pwm')}
|
||||
</option>
|
||||
<option value="1">
|
||||
{t('LedConfig:player.pled-type-rgb')}
|
||||
</option>
|
||||
</FormSelect>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledPin1"
|
||||
hidden={parseInt(values.pledType) !== 0}
|
||||
label={PLED_LABELS[0][values.pledType]}
|
||||
@@ -412,7 +499,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledPin2"
|
||||
hidden={parseInt(values.pledType) !== 0}
|
||||
label={PLED_LABELS[1][values.pledType]}
|
||||
@@ -424,7 +512,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledPin3"
|
||||
hidden={parseInt(values.pledType) !== 0}
|
||||
label={PLED_LABELS[2][values.pledType]}
|
||||
@@ -436,7 +525,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledPin4"
|
||||
hidden={parseInt(values.pledType) !== 0}
|
||||
label={PLED_LABELS[3][values.pledType]}
|
||||
@@ -448,7 +538,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledIndex1"
|
||||
hidden={parseInt(values.pledType) !== 1}
|
||||
label={PLED_LABELS[0][values.pledType]}
|
||||
@@ -460,7 +551,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledIndex2"
|
||||
hidden={parseInt(values.pledType) !== 1}
|
||||
label={PLED_LABELS[1][values.pledType]}
|
||||
@@ -472,7 +564,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledIndex3"
|
||||
hidden={parseInt(values.pledType) !== 1}
|
||||
label={PLED_LABELS[2][values.pledType]}
|
||||
@@ -484,7 +577,8 @@ export default function LEDConfigPage() {
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
/>
|
||||
<FormControl type="number"
|
||||
<FormControl
|
||||
type="number"
|
||||
name="pledIndex4"
|
||||
hidden={parseInt(values.pledType) !== 1}
|
||||
label={PLED_LABELS[3][values.pledType]}
|
||||
@@ -518,15 +612,26 @@ export default function LEDConfigPage() {
|
||||
onChange={(c, e) => setPledColor(values, c)}
|
||||
onDismiss={(e) => setShowPicker(false)}
|
||||
placement="bottom"
|
||||
presetColors={LEDColors.map(c => ({ title: c.name, color: c.value }))}
|
||||
presetColors={LEDColors.map((c) => ({
|
||||
title: c.name,
|
||||
color: c.value,
|
||||
}))}
|
||||
show={showPicker}
|
||||
target={colorPickerTarget}
|
||||
></ColorPicker>
|
||||
</Row>
|
||||
<p hidden={parseInt(values.pledType) !== 0}>{t('LedConfig:player.pwm-sub-header-text')}</p>
|
||||
<p hidden={parseInt(values.pledType) !== 0}>
|
||||
{t('LedConfig:player.pwm-sub-header-text')}
|
||||
</p>
|
||||
<p hidden={parseInt(values.pledType) !== 1}>
|
||||
<Trans ns="LedConfig" i18nKey="player.rgb-sub-header-text" rgbLedStartIndex={rgbLedStartIndex}>
|
||||
For RGB LEDs, the indexes must be after the last LED button defined in <em>RGB LED Button Order</em> section and likely <strong>starts at index {{ rgbLedStartIndex }}</strong>.
|
||||
<Trans
|
||||
ns="LedConfig"
|
||||
i18nKey="player.rgb-sub-header-text"
|
||||
rgbLedStartIndex={rgbLedStartIndex}
|
||||
>
|
||||
For RGB LEDs, the indexes must be after the last LED button
|
||||
defined in <em>RGB LED Button Order</em> section and likely{' '}
|
||||
<strong>starts at index {{ rgbLedStartIndex }}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
</Form.Group>
|
||||
@@ -540,20 +645,25 @@ export default function LEDConfigPage() {
|
||||
</p>
|
||||
<DraggableListGroup
|
||||
groupName="test"
|
||||
titles={[t('LedConfig:rgb-order.available-header-text'), t('LedConfig:rgb-order.assigned-header-text')]}
|
||||
titles={[
|
||||
t('LedConfig:rgb-order.available-header-text'),
|
||||
t('LedConfig:rgb-order.assigned-header-text'),
|
||||
]}
|
||||
dataSources={dataSources}
|
||||
onChange={(a) => ledOrderChanged(a, values.ledsPerButton)}
|
||||
/>
|
||||
</Section>
|
||||
<Button type="submit">{t('Common:button-save-label')}</Button>
|
||||
{saveMessage ? <span className="alert">{saveMessage}</span> : null}
|
||||
<FormContext {...{
|
||||
buttonLabels: buttonLabelType,
|
||||
swapTpShareLabels,
|
||||
ledButtonMap,
|
||||
setDataSources,
|
||||
ledFormat: values.ledFormat
|
||||
}} />
|
||||
<FormContext
|
||||
{...{
|
||||
buttonLabels: buttonLabelType,
|
||||
swapTpShareLabels,
|
||||
ledButtonMap,
|
||||
setDataSources,
|
||||
ledFormat: values.ledFormat,
|
||||
}}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import Section from '../Components/Section';
|
||||
@@ -18,7 +18,8 @@ const errorType = {
|
||||
};
|
||||
|
||||
export default function PinMappingPage() {
|
||||
const { buttonLabels, setButtonLabels, usedPins, updateUsedPins } = useContext(AppContext);
|
||||
const { buttonLabels, setButtonLabels, usedPins, updateUsedPins } =
|
||||
useContext(AppContext);
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [saveMessage, setSaveMessage] = useState('');
|
||||
const [buttonMappings, setButtonMappings] = useState(baseButtonMappings);
|
||||
@@ -29,13 +30,19 @@ export default function PinMappingPage() {
|
||||
|
||||
const { t } = useTranslation('');
|
||||
|
||||
const translatedErrorType = Object.keys(errorType).reduce((a, k) => ({ ...a, [k]: t(`PinMapping:${errorType[k]}`) }), {});
|
||||
const translatedErrorType = Object.keys(errorType).reduce(
|
||||
(a, k) => ({ ...a, [k]: t(`PinMapping:${errorType[k]}`) }),
|
||||
{},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setButtonMappings(await WebApi.getPinMappings(setLoading));
|
||||
const options = await WebApi.getGamepadOptions(setLoading);
|
||||
setButtonLabels({ swapTpShareLabels: options.switchTpShareForDs4 && (options.inputMode === 4) });
|
||||
setButtonLabels({
|
||||
swapTpShareLabels:
|
||||
options.switchTpShareForDs4 && options.inputMode === 4,
|
||||
});
|
||||
}
|
||||
|
||||
fetchData();
|
||||
@@ -43,10 +50,8 @@ export default function PinMappingPage() {
|
||||
|
||||
const handlePinChange = (e, prop) => {
|
||||
const newMappings = { ...buttonMappings };
|
||||
if (e.target.value)
|
||||
newMappings[prop].pin = parseInt(e.target.value);
|
||||
else
|
||||
newMappings[prop].pin = '';
|
||||
if (e.target.value) newMappings[prop].pin = parseInt(e.target.value);
|
||||
else newMappings[prop].pin = '';
|
||||
|
||||
validateMappings(newMappings);
|
||||
};
|
||||
@@ -58,15 +63,18 @@ export default function PinMappingPage() {
|
||||
let mappings = { ...buttonMappings };
|
||||
validateMappings(mappings);
|
||||
|
||||
if (Object.keys(mappings).filter(p => mappings[p].error).length > 0) {
|
||||
if (Object.keys(mappings).filter((p) => mappings[p].error).length > 0) {
|
||||
setSaveMessage(t('Common:errors.validation-error'));
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await WebApi.setPinMappings(mappings);
|
||||
if (success)
|
||||
updateUsedPins();
|
||||
setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message'));
|
||||
if (success) updateUsedPins();
|
||||
setSaveMessage(
|
||||
success
|
||||
? t('Common:saved-success-message')
|
||||
: t('Common:saved-error-message'),
|
||||
);
|
||||
};
|
||||
|
||||
const validateMappings = (mappings) => {
|
||||
@@ -74,32 +82,40 @@ export default function PinMappingPage() {
|
||||
|
||||
// Create some mapped pin groups for easier error checking
|
||||
const mappedPins = buttons
|
||||
.filter(p => mappings[p].pin > -1)
|
||||
.filter((p) => mappings[p].pin > -1)
|
||||
.reduce((a, p) => {
|
||||
a.push(mappings[p].pin);
|
||||
return a;
|
||||
}, []);
|
||||
const mappedPinCounts = mappedPins.reduce((a, p) => ({ ...a, [p]: (a[p] || 0) + 1 }), {});
|
||||
const mappedPinCounts = mappedPins.reduce(
|
||||
(a, p) => ({ ...a, [p]: (a[p] || 0) + 1 }),
|
||||
{},
|
||||
);
|
||||
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);
|
||||
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 = '';
|
||||
|
||||
// Validate required button
|
||||
if ((mappings[button].pin < boards[selectedBoard].minPin || mappings[button].pin > boards[selectedBoard].maxPin) && requiredButtons.filter(b => b === button).length)
|
||||
if (
|
||||
(mappings[button].pin < boards[selectedBoard].minPin ||
|
||||
mappings[button].pin > boards[selectedBoard].maxPin) &&
|
||||
requiredButtons.filter((b) => b === button).length
|
||||
)
|
||||
mappings[button].error = translatedErrorType.required;
|
||||
|
||||
// Identify conflicted pins
|
||||
else if (conflictedPins.indexOf(mappings[button].pin) > -1)
|
||||
mappings[button].error = translatedErrorType.conflict;
|
||||
|
||||
// Identify invalid pin assignments
|
||||
else if (invalidPins.indexOf(mappings[button].pin) > -1)
|
||||
mappings[button].error = translatedErrorType.invalid;
|
||||
|
||||
// Identify used pins
|
||||
else if (otherPins.indexOf(mappings[button].pin) > -1)
|
||||
mappings[button].error = translatedErrorType.used;
|
||||
@@ -111,31 +127,44 @@ export default function PinMappingPage() {
|
||||
|
||||
const renderError = (button) => {
|
||||
if (buttonMappings[button].error === translatedErrorType.required) {
|
||||
return <span key="required" className="error-message">{t('PinMapping:errors.required', {
|
||||
button: BUTTONS[buttonLabelType][button]
|
||||
})}</span>;
|
||||
}
|
||||
else if (buttonMappings[button].error === translatedErrorType.conflict) {
|
||||
return (
|
||||
<span key="required" className="error-message">
|
||||
{t('PinMapping:errors.required', {
|
||||
button: BUTTONS[buttonLabelType][button],
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else if (buttonMappings[button].error === translatedErrorType.conflict) {
|
||||
const conflictedMappings = Object.keys(buttonMappings)
|
||||
.filter(b => b !== button)
|
||||
.filter(b => buttonMappings[b].pin === buttonMappings[button].pin)
|
||||
.map(b => BUTTONS[buttonLabelType][b]);
|
||||
.filter((b) => b !== button)
|
||||
.filter((b) => buttonMappings[b].pin === buttonMappings[button].pin)
|
||||
.map((b) => BUTTONS[buttonLabelType][b]);
|
||||
|
||||
return <span key="conflict" className="error-message">{t('PinMapping:errors.conflict', {
|
||||
pin: buttonMappings[button].pin,
|
||||
conflictedMappings: conflictedMappings.join(', '),
|
||||
})}</span>;
|
||||
}
|
||||
else if (buttonMappings[button].error === translatedErrorType.invalid) {
|
||||
return (
|
||||
<span key="conflict" className="error-message">
|
||||
{t('PinMapping:errors.conflict', {
|
||||
pin: buttonMappings[button].pin,
|
||||
conflictedMappings: conflictedMappings.join(', '),
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else if (buttonMappings[button].error === translatedErrorType.invalid) {
|
||||
console.log(buttonMappings[button].pin);
|
||||
return <span key="invalid" className="error-message">{t('PinMapping:errors.invalid', {
|
||||
pin: buttonMappings[button].pin
|
||||
})}</span>;
|
||||
}
|
||||
else if (buttonMappings[button].error === translatedErrorType.used) {
|
||||
return <span key="used" className="error-message">{t('PinMapping:errors.used', {
|
||||
pin: buttonMappings[button].pin
|
||||
})}</span>;
|
||||
return (
|
||||
<span key="invalid" className="error-message">
|
||||
{t('PinMapping:errors.invalid', {
|
||||
pin: buttonMappings[button].pin,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else if (buttonMappings[button].error === translatedErrorType.used) {
|
||||
return (
|
||||
<span key="used" className="error-message">
|
||||
{t('PinMapping:errors.used', {
|
||||
pin: buttonMappings[button].pin,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
@@ -151,44 +180,70 @@ export default function PinMappingPage() {
|
||||
</div> */}
|
||||
<div className="alert alert-warning">
|
||||
<Trans ns="PinMapping" i18nKey="alert-text">
|
||||
Mapping buttons to pins that aren't connected or available can leave the device in non-functional state. To clear the
|
||||
the invalid configuration go to the <NavLink exact="true" to="/reset-settings">Reset Settings</NavLink> page.
|
||||
Mapping buttons to pins that aren't connected or available can
|
||||
leave the device in non-functional state. To clear the the invalid
|
||||
configuration go to the{' '}
|
||||
<NavLink exact="true" to="/reset-settings">
|
||||
Reset Settings
|
||||
</NavLink>{' '}
|
||||
page.
|
||||
</Trans>
|
||||
</div>
|
||||
<table className="table table-sm pin-mapping-table">
|
||||
<thead className="table">
|
||||
<tr>
|
||||
<th className="table-header-button-label">{BUTTONS[buttonLabelType].label}</th>
|
||||
<th className="table-header-button-label">
|
||||
{BUTTONS[buttonLabelType].label}
|
||||
</th>
|
||||
<th>{t('PinMapping:pin-header-label')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(BUTTONS[buttonLabelType])?.filter(p => p !== 'label' && p !== 'value').map((button, i) => {
|
||||
let label = BUTTONS[buttonLabelType][button];
|
||||
if (button === "S1" && swapTpShareLabels && buttonLabelType === "ps4") {
|
||||
label = BUTTONS[buttonLabelType]["A2"];
|
||||
}
|
||||
if (button === "A2" && swapTpShareLabels && buttonLabelType === "ps4") {
|
||||
label = BUTTONS[buttonLabelType]["S1"];
|
||||
}
|
||||
return <tr key={`button-map-${i}`} className={validated && !!buttonMappings[button].error ? "table-danger" : ""}>
|
||||
<td>{label}</td>
|
||||
<td>
|
||||
<Form.Control
|
||||
type="number"
|
||||
className="pin-input form-control-sm"
|
||||
value={buttonMappings[button].pin}
|
||||
min={-1}
|
||||
max={boards[selectedBoard].maxPin}
|
||||
isInvalid={buttonMappings[button].error}
|
||||
onChange={(e) => handlePinChange(e, button)}
|
||||
></Form.Control>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{renderError(button)}
|
||||
</Form.Control.Feedback>
|
||||
</td>
|
||||
</tr>
|
||||
})}
|
||||
{Object.keys(BUTTONS[buttonLabelType])
|
||||
?.filter((p) => p !== 'label' && p !== 'value')
|
||||
.map((button, i) => {
|
||||
let label = BUTTONS[buttonLabelType][button];
|
||||
if (
|
||||
button === 'S1' &&
|
||||
swapTpShareLabels &&
|
||||
buttonLabelType === 'ps4'
|
||||
) {
|
||||
label = BUTTONS[buttonLabelType]['A2'];
|
||||
}
|
||||
if (
|
||||
button === 'A2' &&
|
||||
swapTpShareLabels &&
|
||||
buttonLabelType === 'ps4'
|
||||
) {
|
||||
label = BUTTONS[buttonLabelType]['S1'];
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
key={`button-map-${i}`}
|
||||
className={
|
||||
validated && !!buttonMappings[button].error
|
||||
? 'table-danger'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<td>{label}</td>
|
||||
<td>
|
||||
<Form.Control
|
||||
type="number"
|
||||
className="pin-input form-control-sm"
|
||||
value={buttonMappings[button].pin}
|
||||
min={-1}
|
||||
max={boards[selectedBoard].maxPin}
|
||||
isInvalid={buttonMappings[button].error}
|
||||
onChange={(e) => handlePinChange(e, button)}
|
||||
></Form.Control>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{renderError(button)}
|
||||
</Form.Control.Feedback>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<Button type="submit">{t('Common:button-save-label')}</Button>
|
||||
|
||||
@@ -31,4 +31,3 @@ table.pin-mapping-table {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import Section from '../Components/Section';
|
||||
import WebApi, { baseProfileOptions, baseButtonMappings } from '../Services/WebApi';
|
||||
import WebApi, {
|
||||
baseProfileOptions,
|
||||
baseButtonMappings,
|
||||
} from '../Services/WebApi';
|
||||
import boards from '../Data/Boards.json';
|
||||
import { BUTTONS } from '../Data/Buttons';
|
||||
import './PinMappings.scss';
|
||||
@@ -18,7 +21,8 @@ const errorType = {
|
||||
};
|
||||
|
||||
export default function ProfileOptionsPage() {
|
||||
const { buttonLabels, setButtonLabels, usedPins, updateUsedPins } = useContext(AppContext);
|
||||
const { buttonLabels, setButtonLabels, usedPins, updateUsedPins } =
|
||||
useContext(AppContext);
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [saveMessage, setSaveMessage] = useState('');
|
||||
const [buttonMappings, setButtonMappings] = useState(baseButtonMappings);
|
||||
@@ -29,7 +33,10 @@ export default function ProfileOptionsPage() {
|
||||
|
||||
const { t } = useTranslation('');
|
||||
|
||||
const translatedErrorType = Object.keys(errorType).reduce((a, k) => ({ ...a, [k]: t(`ProfileOptions:${errorType[k]}`) }), {});
|
||||
const translatedErrorType = Object.keys(errorType).reduce(
|
||||
(a, k) => ({ ...a, [k]: t(`ProfileOptions:${errorType[k]}`) }),
|
||||
{},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
@@ -44,9 +51,10 @@ export default function ProfileOptionsPage() {
|
||||
const handlePinChange = (e, index, key) => {
|
||||
const newProfileOptions = { ...profileOptions };
|
||||
if (e.target.value)
|
||||
newProfileOptions['alternativePinMappings'][index][key].pin = parseInt(e.target.value);
|
||||
else
|
||||
newProfileOptions['alternativePinMappings'][index][key].pin = '';
|
||||
newProfileOptions['alternativePinMappings'][index][key].pin = parseInt(
|
||||
e.target.value,
|
||||
);
|
||||
else newProfileOptions['alternativePinMappings'][index][key].pin = '';
|
||||
|
||||
validateMappings(newProfileOptions);
|
||||
};
|
||||
@@ -58,15 +66,18 @@ export default function ProfileOptionsPage() {
|
||||
let mappings = { ...profileOptions };
|
||||
validateMappings(mappings);
|
||||
|
||||
if (Object.keys(mappings).filter(p => mappings[p].error).length > 0) {
|
||||
if (Object.keys(mappings).filter((p) => mappings[p].error).length > 0) {
|
||||
setSaveMessage(t('Common:errors.validation-error'));
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await WebApi.setProfileOptions(mappings);
|
||||
if (success)
|
||||
updateUsedPins();
|
||||
setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message'));
|
||||
if (success) updateUsedPins();
|
||||
setSaveMessage(
|
||||
success
|
||||
? t('Common:saved-success-message')
|
||||
: t('Common:saved-error-message'),
|
||||
);
|
||||
};
|
||||
|
||||
const validateMappings = (mappings) => {
|
||||
@@ -75,32 +86,40 @@ export default function ProfileOptionsPage() {
|
||||
|
||||
// Create some mapped pin groups for easier error checking
|
||||
const mappedPins = buttons
|
||||
.filter(p => altMappings[p].pin > -1)
|
||||
.filter((p) => altMappings[p].pin > -1)
|
||||
.reduce((a, p) => {
|
||||
a.push(altMappings[p].pin);
|
||||
return a;
|
||||
}, []);
|
||||
const mappedPinCounts = mappedPins.reduce((a, p) => ({ ...a, [p]: (a[p] || 0) + 1 }), {});
|
||||
const mappedPinCounts = mappedPins.reduce(
|
||||
(a, p) => ({ ...a, [p]: (a[p] || 0) + 1 }),
|
||||
{},
|
||||
);
|
||||
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);
|
||||
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) {
|
||||
altMappings[button].error = '';
|
||||
|
||||
// Validate required button
|
||||
if ((altMappings[button].pin < boards[selectedBoard].minPin || altMappings[button].pin > boards[selectedBoard].maxPin) && requiredButtons.filter(b => b === button).length)
|
||||
if (
|
||||
(altMappings[button].pin < boards[selectedBoard].minPin ||
|
||||
altMappings[button].pin > boards[selectedBoard].maxPin) &&
|
||||
requiredButtons.filter((b) => b === button).length
|
||||
)
|
||||
altMappings[button].error = translatedErrorType.required;
|
||||
|
||||
// Identify conflicted pins
|
||||
else if (conflictedPins.indexOf(altMappings[button].pin) > -1)
|
||||
altMappings[button].error = translatedErrorType.conflict;
|
||||
|
||||
// Identify invalid pin assignments
|
||||
else if (invalidPins.indexOf(altMappings[button].pin) > -1)
|
||||
altMappings[button].error = translatedErrorType.invalid;
|
||||
|
||||
// Identify used pins
|
||||
else if (otherPins.indexOf(altMappings[button].pin) > -1)
|
||||
altMappings[button].error = translatedErrorType.used;
|
||||
@@ -112,49 +131,84 @@ export default function ProfileOptionsPage() {
|
||||
};
|
||||
|
||||
const pinCell = (profile, button) => {
|
||||
return <td>
|
||||
<Form.Control
|
||||
type="number"
|
||||
className="pin-input form-control-sm"
|
||||
value={profileOptions['alternativePinMappings'][profile][button].pin}
|
||||
min={-1}
|
||||
max={boards[selectedBoard].maxPin}
|
||||
isInvalid={profileOptions['alternativePinMappings'][profile][button].error}
|
||||
onChange={(e) => handlePinChange(e, profile, button)}
|
||||
></Form.Control>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{renderError(profile, button)}
|
||||
</Form.Control.Feedback>
|
||||
</td>
|
||||
}
|
||||
return (
|
||||
<td>
|
||||
<Form.Control
|
||||
type="number"
|
||||
className="pin-input form-control-sm"
|
||||
value={profileOptions['alternativePinMappings'][profile][button].pin}
|
||||
min={-1}
|
||||
max={boards[selectedBoard].maxPin}
|
||||
isInvalid={
|
||||
profileOptions['alternativePinMappings'][profile][button].error
|
||||
}
|
||||
onChange={(e) => handlePinChange(e, profile, button)}
|
||||
></Form.Control>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{renderError(profile, button)}
|
||||
</Form.Control.Feedback>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
const renderError = (index, button) => {
|
||||
if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.required) {
|
||||
return <span key="required" className="error-message">{t('PinMapping:errors.required', {
|
||||
button: BUTTONS[buttonLabelType][button]
|
||||
})}</span>;
|
||||
}
|
||||
else if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.conflict) {
|
||||
const conflictedMappings = Object.keys(profileOptions['alternativePinMappings'][index])
|
||||
.filter(b => b !== button)
|
||||
.filter(b => profileOptions['alternativePinMappings'][index][b].pin === profileOptions['alternativePinMappings'][index][button].pin)
|
||||
.map(b => BUTTONS[buttonLabelType][b]);
|
||||
if (
|
||||
profileOptions['alternativePinMappings'][index][button].error ===
|
||||
translatedErrorType.required
|
||||
) {
|
||||
return (
|
||||
<span key="required" className="error-message">
|
||||
{t('PinMapping:errors.required', {
|
||||
button: BUTTONS[buttonLabelType][button],
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else if (
|
||||
profileOptions['alternativePinMappings'][index][button].error ===
|
||||
translatedErrorType.conflict
|
||||
) {
|
||||
const conflictedMappings = Object.keys(
|
||||
profileOptions['alternativePinMappings'][index],
|
||||
)
|
||||
.filter((b) => b !== button)
|
||||
.filter(
|
||||
(b) =>
|
||||
profileOptions['alternativePinMappings'][index][b].pin ===
|
||||
profileOptions['alternativePinMappings'][index][button].pin,
|
||||
)
|
||||
.map((b) => BUTTONS[buttonLabelType][b]);
|
||||
|
||||
return <span key="conflict" className="error-message">{t('PinMapping:errors.conflict', {
|
||||
pin: profileOptions['alternativePinMappings'][index][button].pin,
|
||||
conflictedMappings: conflictedMappings.join(', '),
|
||||
})}</span>;
|
||||
}
|
||||
else if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.invalid) {
|
||||
return (
|
||||
<span key="conflict" className="error-message">
|
||||
{t('PinMapping:errors.conflict', {
|
||||
pin: profileOptions['alternativePinMappings'][index][button].pin,
|
||||
conflictedMappings: conflictedMappings.join(', '),
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else if (
|
||||
profileOptions['alternativePinMappings'][index][button].error ===
|
||||
translatedErrorType.invalid
|
||||
) {
|
||||
console.log(profileOptions['alternativePinMappings'][index][button].pin);
|
||||
return <span key="invalid" className="error-message">{t('PinMapping:errors.invalid', {
|
||||
pin: profileOptions['alternativePinMappings'][index][button].pin
|
||||
})}</span>;
|
||||
}
|
||||
else if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.used) {
|
||||
return <span key="used" className="error-message">{t('PinMapping:errors.used', {
|
||||
pin: profileOptions['alternativePinMappings'][index][button].pin
|
||||
})}</span>;
|
||||
return (
|
||||
<span key="invalid" className="error-message">
|
||||
{t('PinMapping:errors.invalid', {
|
||||
pin: profileOptions['alternativePinMappings'][index][button].pin,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
} else if (
|
||||
profileOptions['alternativePinMappings'][index][button].error ===
|
||||
translatedErrorType.used
|
||||
) {
|
||||
return (
|
||||
<span key="used" className="error-message">
|
||||
{t('PinMapping:errors.used', {
|
||||
pin: profileOptions['alternativePinMappings'][index][button].pin,
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
@@ -163,15 +217,34 @@ export default function ProfileOptionsPage() {
|
||||
return (
|
||||
<Section title={t('ProfileSettings:header-text')}>
|
||||
<p>{t('ProfileSettings:profile-pins-desc')}</p>
|
||||
<pre> {String(buttonMappings['Up'].pin).padStart(2)}<br />
|
||||
{String(buttonMappings['Left'].pin).padStart(2)} {String(buttonMappings['Right'].pin).padStart(2)} {String(buttonMappings['B3'].pin).padStart(2)} {String(buttonMappings['B4'].pin).padStart(2)} {String(buttonMappings['R1'].pin).padStart(2)} {String(buttonMappings['L1'].pin).padStart(2)}<br />
|
||||
{String(buttonMappings['Down'].pin).padStart(2)} {String(buttonMappings['B1'].pin).padStart(2)} {String(buttonMappings['B2'].pin).padStart(2)} {String(buttonMappings['R2'].pin).padStart(2)} {String(buttonMappings['L2'].pin).padStart(2)}</pre>
|
||||
<p><b>{t('ProfileSettings:profile-pins-warning')}</b></p>
|
||||
<pre>
|
||||
{String(buttonMappings['Up'].pin).padStart(2)}
|
||||
<br />
|
||||
{String(buttonMappings['Left'].pin).padStart(2)}
|
||||
{String(buttonMappings['Right'].pin).padStart(2)}
|
||||
|
||||
{String(buttonMappings['B3'].pin).padStart(2)}{' '}
|
||||
{String(buttonMappings['B4'].pin).padStart(2)}{' '}
|
||||
{String(buttonMappings['R1'].pin).padStart(2)}{' '}
|
||||
{String(buttonMappings['L1'].pin).padStart(2)}
|
||||
<br />
|
||||
{String(buttonMappings['Down'].pin).padStart(2)}
|
||||
|
||||
{String(buttonMappings['B1'].pin).padStart(2)}{' '}
|
||||
{String(buttonMappings['B2'].pin).padStart(2)}{' '}
|
||||
{String(buttonMappings['R2'].pin).padStart(2)}{' '}
|
||||
{String(buttonMappings['L2'].pin).padStart(2)}
|
||||
</pre>
|
||||
<p>
|
||||
<b>{t('ProfileSettings:profile-pins-warning')}</b>
|
||||
</p>
|
||||
<Form noValidate validated={validated} onSubmit={handleSubmit}>
|
||||
<table className="table table-sm pin-mapping-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="table-header-button-label">{BUTTONS[buttonLabelType].label}</th>
|
||||
<th className="table-header-button-label">
|
||||
{BUTTONS[buttonLabelType].label}
|
||||
</th>
|
||||
<th>{t('ProfileSettings:profile-1')}</th>
|
||||
<th>{t('ProfileSettings:profile-2')}</th>
|
||||
<th>{t('ProfileSettings:profile-3')}</th>
|
||||
@@ -179,16 +252,20 @@ export default function ProfileOptionsPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{console.log(Object.keys(profileOptions['alternativePinMappings'][0]))}
|
||||
{Object.keys(profileOptions['alternativePinMappings'][0]).map((key) => (
|
||||
<tr key={key}>
|
||||
<td>{BUTTONS[buttonLabelType][key]}</td>
|
||||
<td>{buttonMappings[key].pin}</td>
|
||||
{pinCell(0, key)}
|
||||
{pinCell(1, key)}
|
||||
{pinCell(2, key)}
|
||||
</tr>
|
||||
))}
|
||||
{console.log(
|
||||
Object.keys(profileOptions['alternativePinMappings'][0]),
|
||||
)}
|
||||
{Object.keys(profileOptions['alternativePinMappings'][0]).map(
|
||||
(key) => (
|
||||
<tr key={key}>
|
||||
<td>{BUTTONS[buttonLabelType][key]}</td>
|
||||
<td>{buttonMappings[key].pin}</td>
|
||||
{pinCell(0, key)}
|
||||
{pinCell(1, key)}
|
||||
{pinCell(2, key)}
|
||||
</tr>
|
||||
),
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<Button type="submit">{t('Common:button-save-label')}</Button>
|
||||
|
||||
@@ -11,7 +11,6 @@ export default function ResetSettingsPage() {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
||||
if (window.confirm(t('ResetSettings:confirm-text'))) {
|
||||
const result = await WebApi.resetSettings();
|
||||
console.log(result);
|
||||
@@ -25,12 +24,17 @@ export default function ResetSettingsPage() {
|
||||
<DangerSection title={t('ResetSettings:header-text')}>
|
||||
<Trans ns="ResetSettings" i18nKey="sub-header-text">
|
||||
<p className="card-text">
|
||||
This option resets all saved configurations on your controller. Use this option as a
|
||||
last resort or when trying to diagnose odd issues with your controller.
|
||||
This option resets all saved configurations on your controller. Use
|
||||
this option as a last resort or when trying to diagnose odd issues
|
||||
with your controller.
|
||||
</p>
|
||||
<p className="card-text">
|
||||
This process will automatically reset the controller.
|
||||
</p>
|
||||
<p className="card-text">This process will automatically reset the controller.</p>
|
||||
</Trans>
|
||||
<button className="btn btn-danger" onClick={resetSettings}>{t('Common:button-reset-settings-label')}</button>
|
||||
<button className="btn btn-danger" onClick={resetSettings}>
|
||||
{t('Common:button-reset-settings-label')}
|
||||
</button>
|
||||
</DangerSection>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
||||
import { AppContext } from '../Contexts/AppContext';
|
||||
import { Button, Form, Modal } from 'react-bootstrap';
|
||||
import { Formik, useFormikContext } from 'formik';
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import * as yup from 'yup';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -16,7 +16,7 @@ const INPUT_MODES = [
|
||||
{ labelKey: 'input-mode-options.nintendo-switch', value: 1 },
|
||||
{ labelKey: 'input-mode-options.ps3', value: 2 },
|
||||
{ labelKey: 'input-mode-options.keyboard', value: 3 },
|
||||
{ labelKey: 'input-mode-options.ps4', value: PS4Mode }
|
||||
{ labelKey: 'input-mode-options.ps4', value: PS4Mode },
|
||||
];
|
||||
|
||||
const DPAD_MODES = [
|
||||
@@ -58,7 +58,7 @@ const HOTKEY_ACTIONS = [
|
||||
{ labelKey: 'hotkey-actions.load-profile-2', value: 16 },
|
||||
{ labelKey: 'hotkey-actions.load-profile-3', value: 17 },
|
||||
{ labelKey: 'hotkey-actions.load-profile-4', value: 18 },
|
||||
{ labelKey: 'hotkey-actions.l3-button', value: 19 },
|
||||
{ labelKey: 'hotkey-actions.l3-button', value: 19 },
|
||||
{ labelKey: 'hotkey-actions.r3-button', value: 20 },
|
||||
{ labelKey: 'hotkey-actions.touchpad-button', value: 21 },
|
||||
{ labelKey: 'hotkey-actions.reboot-default', value: 22 },
|
||||
@@ -72,29 +72,61 @@ const FORCED_SETUP_MODES = [
|
||||
];
|
||||
|
||||
const hotkeySchema = {
|
||||
action: yup.number().required().oneOf(HOTKEY_ACTIONS.map(o => o.value)).label('Hotkey Action'),
|
||||
action: yup
|
||||
.number()
|
||||
.required()
|
||||
.oneOf(HOTKEY_ACTIONS.map((o) => o.value))
|
||||
.label('Hotkey Action'),
|
||||
buttonsMask: yup.number().required().label('Button Mask'),
|
||||
auxMask: yup.number().required().label('Function Key')
|
||||
auxMask: yup.number().required().label('Function Key'),
|
||||
};
|
||||
|
||||
const hotkeyFields = Array(12).fill(0).reduce((acc, a, i) => {
|
||||
const number = String(i + 1).padStart(2, '0');
|
||||
const newSchema = yup.object().label('Hotkey ' + number).shape({ ...hotkeySchema });
|
||||
acc["hotkey" + number] = newSchema;
|
||||
return acc;
|
||||
}, {});
|
||||
const hotkeyFields = Array(12)
|
||||
.fill(0)
|
||||
.reduce((acc, a, i) => {
|
||||
const number = String(i + 1).padStart(2, '0');
|
||||
const newSchema = yup
|
||||
.object()
|
||||
.label('Hotkey ' + number)
|
||||
.shape({ ...hotkeySchema });
|
||||
acc['hotkey' + number] = newSchema;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const schema = yup.object().shape({
|
||||
dpadMode : yup.number().required().oneOf(DPAD_MODES.map(o => o.value)).label('D-Pad Mode'),
|
||||
dpadMode: yup
|
||||
.number()
|
||||
.required()
|
||||
.oneOf(DPAD_MODES.map((o) => o.value))
|
||||
.label('D-Pad Mode'),
|
||||
...hotkeyFields,
|
||||
inputMode: yup.number().required().oneOf(INPUT_MODES.map(o => o.value)).label('Input Mode'),
|
||||
socdMode : yup.number().required().oneOf(SOCD_MODES.map(o => o.value)).label('SOCD Cleaning Mode'),
|
||||
switchTpShareForDs4: yup.number().required().label('Switch Touchpad and Share'),
|
||||
forcedSetupMode : yup.number().required().oneOf(FORCED_SETUP_MODES.map(o => o.value)).label('SOCD Cleaning Mode'),
|
||||
inputMode: yup
|
||||
.number()
|
||||
.required()
|
||||
.oneOf(INPUT_MODES.map((o) => o.value))
|
||||
.label('Input Mode'),
|
||||
socdMode: yup
|
||||
.number()
|
||||
.required()
|
||||
.oneOf(SOCD_MODES.map((o) => o.value))
|
||||
.label('SOCD Cleaning Mode'),
|
||||
switchTpShareForDs4: yup
|
||||
.number()
|
||||
.required()
|
||||
.label('Switch Touchpad and Share'),
|
||||
forcedSetupMode: yup
|
||||
.number()
|
||||
.required()
|
||||
.oneOf(FORCED_SETUP_MODES.map((o) => o.value))
|
||||
.label('SOCD Cleaning Mode'),
|
||||
lockHotkeys: yup.number().required().label('Lock Hotkeys'),
|
||||
fourWayMode: yup.number().required().label('4-Way Joystick Mode'),
|
||||
profileNumber: yup.number().required().label('Profile Number'),
|
||||
ps4ControllerType: yup.number().required().oneOf(PS4_MODES.map(o => o.value)).label('PS4 Controller Type'),
|
||||
ps4ControllerType: yup
|
||||
.number()
|
||||
.required()
|
||||
.oneOf(PS4_MODES.map((o) => o.value))
|
||||
.label('PS4 Controller Type'),
|
||||
});
|
||||
|
||||
const FormContext = ({ setButtonLabels }) => {
|
||||
@@ -103,44 +135,45 @@ const FormContext = ({ setButtonLabels }) => {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
const options = await WebApi.getGamepadOptions(setLoading)
|
||||
const options = await WebApi.getGamepadOptions(setLoading);
|
||||
setValues(options);
|
||||
setButtonLabels({ swapTpShareLabels: (options.switchTpShareForDs4 === 1) && (options.inputMode === 4) });
|
||||
setButtonLabels({
|
||||
swapTpShareLabels:
|
||||
options.switchTpShareForDs4 === 1 && options.inputMode === 4,
|
||||
});
|
||||
}
|
||||
fetchData();
|
||||
}, [setValues]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!values.dpadMode)
|
||||
values.dpadMode = parseInt(values.dpadMode);
|
||||
if (!!values.inputMode)
|
||||
values.inputMode = parseInt(values.inputMode);
|
||||
if (!!values.socdMode)
|
||||
values.socdMode = parseInt(values.socdMode);
|
||||
if (!!values.dpadMode) values.dpadMode = parseInt(values.dpadMode);
|
||||
if (!!values.inputMode) values.inputMode = parseInt(values.inputMode);
|
||||
if (!!values.socdMode) values.socdMode = parseInt(values.socdMode);
|
||||
if (!!values.switchTpShareForDs4)
|
||||
values.switchTpShareForDs4 = parseInt(values.switchTpShareForDs4);
|
||||
if (!!values.forcedSetupMode)
|
||||
values.forcedSetupMode = parseInt(values.forcedSetupMode);
|
||||
if (!!values.lockHotkeys)
|
||||
values.lockHotkeys = parseInt(values.lockHotkeys);
|
||||
if (!!values.fourWayMode)
|
||||
values.fourWayMode = parseInt(values.fourWayMode);
|
||||
if (!!values.lockHotkeys) values.lockHotkeys = parseInt(values.lockHotkeys);
|
||||
if (!!values.fourWayMode) values.fourWayMode = parseInt(values.fourWayMode);
|
||||
if (!!values.profileNumber)
|
||||
values.profileNumber = parseInt(values.profileNumber);
|
||||
if (!!values.ps4ControllerType)
|
||||
values.ps4ControllerType = parseInt(values.ps4ControllerType);
|
||||
if (!!values.ps4ControllerType)
|
||||
values.ps4ControllerType = parseInt(values.ps4ControllerType);
|
||||
|
||||
setButtonLabels({ swapTpShareLabels: (values.switchTpShareForDs4 === 1) && (values.inputMode === 4) });
|
||||
setButtonLabels({
|
||||
swapTpShareLabels:
|
||||
values.switchTpShareForDs4 === 1 && values.inputMode === 4,
|
||||
});
|
||||
|
||||
Object.keys(hotkeyFields).forEach(a => {
|
||||
Object.keys(hotkeyFields).forEach((a) => {
|
||||
const value = values[a];
|
||||
if (value) {
|
||||
values[a] = {
|
||||
action: parseInt(value.action),
|
||||
buttonsMask: parseInt(value.buttonsMask),
|
||||
auxMask: parseInt(value.auxMask)
|
||||
}
|
||||
};
|
||||
auxMask: parseInt(value.auxMask),
|
||||
};
|
||||
}
|
||||
});
|
||||
}, [values, setValues]);
|
||||
|
||||
@@ -150,12 +183,12 @@ const FormContext = ({ setButtonLabels }) => {
|
||||
export default function SettingsPage() {
|
||||
const { buttonLabels, setButtonLabels } = useContext(AppContext);
|
||||
const [saveMessage, setSaveMessage] = useState('');
|
||||
const [warning, setWarning] = useState({ show: false, acceptText: ''});
|
||||
const [warning, setWarning] = useState({ show: false, acceptText: '' });
|
||||
|
||||
const WARNING_CHECK_TEXT = "GP2040-CE";
|
||||
const WARNING_CHECK_TEXT = 'GP2040-CE';
|
||||
|
||||
const handleWarningClose = async (accepted, values, setFieldValue) => {
|
||||
setWarning({ show: false, acceptText: ''});
|
||||
setWarning({ show: false, acceptText: '' });
|
||||
if (accepted) await saveSettings(values);
|
||||
else setFieldValue('forcedSetupMode', 0);
|
||||
};
|
||||
@@ -166,25 +199,35 @@ export default function SettingsPage() {
|
||||
|
||||
const saveSettings = async (values) => {
|
||||
const success = await WebApi.setGamepadOptions(values);
|
||||
setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message'));
|
||||
setSaveMessage(
|
||||
success
|
||||
? t('Common:saved-success-message')
|
||||
: t('Common:saved-error-message'),
|
||||
);
|
||||
};
|
||||
|
||||
const onSuccess = async (values) => {
|
||||
if (values.forcedSetupMode > 1) { setWarning({ show: true, acceptText: ''}); }
|
||||
else { await saveSettings(values); }
|
||||
if (values.forcedSetupMode > 1) {
|
||||
setWarning({ show: true, acceptText: '' });
|
||||
} else {
|
||||
await saveSettings(values);
|
||||
}
|
||||
};
|
||||
|
||||
const translateArray = (array) => {
|
||||
return array.map(({ labelKey, value }) => {
|
||||
return { label: t(`SettingsPage:${labelKey}`), value };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { buttonLabelType, swapTpShareLabels } = buttonLabels;
|
||||
|
||||
const buttonLabelS1 = BUTTONS[buttonLabelType][(swapTpShareLabels && buttonLabelType === "ps4") ? "A2" : "S1"];
|
||||
const buttonLabelS2 = BUTTONS[buttonLabelType]["S2"];
|
||||
const buttonLabelA1 = BUTTONS[buttonLabelType]["A1"];
|
||||
const buttonLabelS1 =
|
||||
BUTTONS[buttonLabelType][
|
||||
swapTpShareLabels && buttonLabelType === 'ps4' ? 'A2' : 'S1'
|
||||
];
|
||||
const buttonLabelS2 = BUTTONS[buttonLabelType]['S2'];
|
||||
const buttonLabelA1 = BUTTONS[buttonLabelType]['A1'];
|
||||
|
||||
const { t } = useTranslation('');
|
||||
|
||||
@@ -197,189 +240,383 @@ export default function SettingsPage() {
|
||||
|
||||
return (
|
||||
<Formik validationSchema={schema} onSubmit={onSuccess} initialValues={{}}>
|
||||
{({
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
values,
|
||||
errors,
|
||||
setFieldValue
|
||||
}) => console.log('errors', errors) || (
|
||||
<div>
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Section title={t('SettingsPage:settings-header-text')}>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:input-mode-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select name="inputMode" className="form-select-sm" value={values.inputMode} onChange={handleChange} isInvalid={errors.inputMode}>
|
||||
{translatedInputModes.map((o, i) => <option key={`button-inputMode-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">{errors.inputMode}</Form.Control.Feedback>
|
||||
</div>
|
||||
{(values.inputMode === PS4Mode) &&
|
||||
<div className="col-sm-3">
|
||||
<Form.Check
|
||||
label={t('SettingsPage:input-mode-extra-label')}
|
||||
type="switch"
|
||||
name="switchTpShareForDs4"
|
||||
isInvalid={false}
|
||||
checked={Boolean(values.switchTpShareForDs4)}
|
||||
onChange={(e) => { setFieldValue("switchTpShareForDs4", e.target.checked ? 1 : 0); }}
|
||||
/>
|
||||
</div>}
|
||||
{(values.inputMode === PS4Mode) &&
|
||||
<div className="col-sm-3">
|
||||
<Form.Select
|
||||
name="ps4ControllerType"
|
||||
className="form-select-sm"
|
||||
value={values.ps4ControllerType}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors.ps4ControllerType}>
|
||||
{translatedPS4ControllerTypeModes.map((o, i) => <option key={`button-ps4ControllerType-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</Form.Select>
|
||||
</div>}
|
||||
{(values.inputMode === PS4Mode) &&
|
||||
<div className="mb-3">
|
||||
<Trans ns="SettingsPage" i18nKey="ps4-compatibility-label">
|
||||
For <strong>PS5 compatibility</strong>, use "Arcade Stick" and enable PS Passthrough add-on<br/>For <strong>PS4 support</strong>, use "Controller" and enable PS4 Mode add-on if you have the necessary files
|
||||
</Trans>
|
||||
</div>}
|
||||
</Form.Group>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:d-pad-mode-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select name="dpadMode" className="form-select-sm" value={values.dpadMode} onChange={handleChange} isInvalid={errors.dpadMode}>
|
||||
{translatedDpadModes.map((o, i) => <option key={`button-dpadMode-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">{errors.dpadMode}</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:socd-cleaning-mode-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select name="socdMode" className="form-select-sm" value={values.socdMode} onChange={handleChange} isInvalid={errors.socdMode}>
|
||||
{translatedSocdModes.map((o, i) => <option key={`button-socdMode-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">{errors.socdMode}</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
<p>{t('SettingsPage:socd-cleaning-mode-note')}</p>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:forced-setup-mode-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select name="forcedSetupMode" className="form-select-sm" value={values.forcedSetupMode} onChange={handleChange} isInvalid={errors.forcedSetupMode}>
|
||||
{translatedForcedSetupModes.map((o, i) => <option key={`button-forcedSetupMode-option-${i}`} value={o.value}>{o.label}</option>)}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">{errors.forcedSetupMode}</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
<Form.Check
|
||||
label={t('SettingsPage:4-way-joystick-mode-label')}
|
||||
type="switch"
|
||||
id="fourWayMode"
|
||||
isInvalid={false}
|
||||
checked={Boolean(values.fourWayMode)}
|
||||
onChange={(e) => { setFieldValue("fourWayMode", e.target.checked ? 1 : 0); }}
|
||||
/>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:profile-number-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Control
|
||||
type="number"
|
||||
className="row mb-3"
|
||||
value={values.profileNumber}
|
||||
min={1}
|
||||
max={4}
|
||||
isInvalid={false}
|
||||
onChange={(e) => { setFieldValue("profileNumber", parseInt(e.target.value));}}
|
||||
></Form.Control>
|
||||
</div>
|
||||
</Form.Group>
|
||||
</Section>
|
||||
<Section title={t('SettingsPage:hotkey-settings-label')}>
|
||||
<div className="mb-3">
|
||||
<Trans ns="SettingsPage" i18nKey="hotkey-settings-sub-header">
|
||||
The <strong>Fn</strong> slider provides a mappable Function button in the <NavLink exact="true" to="/pin-mapping">Pin Mapping</NavLink> page. By selecting the Fn slider option, the Function button must be held along with the selected hotkey settings.
|
||||
<br/>Additionally, select <strong>None</strong> from the dropdown to unassign any button.
|
||||
</Trans>
|
||||
</div>
|
||||
{values.fnButtonPin === -1 && <div className="alert alert-warning">{t('SettingsPage:hotkey-settings-warning')}</div> }
|
||||
<div id="Hotkeys"
|
||||
hidden={values.lockHotkeys}>
|
||||
{Object.keys(hotkeyFields).map((o, i) =>
|
||||
<Form.Group key={`hotkey-${i}`} className="row mb-3">
|
||||
<div className="col-sm-auto">
|
||||
<Form.Check name={`${o}.auxMask`} label=" Fn" type="switch" className="form-select-sm" disabled={values.fnButtonPin === -1} checked={values[o] && !!(values[o]?.auxMask)} onChange={(e) => { setFieldValue(`${o}.auxMask`, e.target.checked ? 32768 : 0)}} isInvalid={errors[o] && errors[o]?.auxMask} />
|
||||
<Form.Control.Feedback type="invalid">{errors[o] && errors[o]?.action}</Form.Control.Feedback>
|
||||
</div>
|
||||
<span className="col-sm-auto">+</span>
|
||||
{BUTTON_MASKS.map(mask => ((values[o] && values[o]?.buttonsMask & mask.value) ?
|
||||
[<div className="col-sm-auto">
|
||||
{({ handleSubmit, handleChange, values, errors, setFieldValue }) =>
|
||||
console.log('errors', errors) || (
|
||||
<div>
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Section title={t('SettingsPage:settings-header-text')}>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:input-mode-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select
|
||||
name={`${o}.buttonsMask`}
|
||||
className="form-select-sm sm-1"
|
||||
groupClassName="mb-3"
|
||||
value={values[o] && (values[o]?.buttonsMask & mask.value)}
|
||||
error={errors[o] && errors[o]?.buttonsMask}
|
||||
isInvalid={errors[o] && errors[o]?.buttonsMask}
|
||||
onChange={(e) => { setFieldValue(`${o}.buttonsMask`, (values[o] && values[o]?.buttonsMask ^ mask.value) | e.target.value); }}>
|
||||
{BUTTON_MASKS.map((o, i2) => <option key={`hotkey-${i}-button${i2}`} value={o.value}>{o.label}</option>)}
|
||||
</Form.Select>
|
||||
</div>, <span className="col-sm-auto">+</span>] : <></>))}
|
||||
<div className="col-sm-auto">
|
||||
<Form.Select
|
||||
name={`${o}.buttonsMask`}
|
||||
className="form-select-sm sm-1"
|
||||
groupClassName="mb-3"
|
||||
value={0}
|
||||
onChange={(e) => { setFieldValue(`${o}.buttonsMask`, (values[o] && values[o]?.buttonsMask) | e.target.value); }}>
|
||||
{BUTTON_MASKS.map((o, i2) => <option key={`hotkey-${i}-buttonZero-${i2}`} value={o.value}>{o.label}</option>)}
|
||||
name="inputMode"
|
||||
className="form-select-sm"
|
||||
value={values.inputMode}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors.inputMode}
|
||||
>
|
||||
{translatedInputModes.map((o, i) => (
|
||||
<option
|
||||
key={`button-inputMode-option-${i}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.inputMode}
|
||||
</Form.Control.Feedback>
|
||||
</div>
|
||||
<span className="col-sm-auto">=</span>
|
||||
<div className="col-sm-auto">
|
||||
<Form.Select name={`${o}.action`} className="form-select-sm" value={values[o] && values[o]?.action} onChange={handleChange} isInvalid={errors[o] && errors[o]?.action}>
|
||||
{translatedHotkeyActions.map((o, i) => <option key={`hotkey-action-${i}`} value={o.value}>{o.label}</option>)}
|
||||
{values.inputMode === PS4Mode && (
|
||||
<div className="col-sm-3">
|
||||
<Form.Check
|
||||
label={t('SettingsPage:input-mode-extra-label')}
|
||||
type="switch"
|
||||
name="switchTpShareForDs4"
|
||||
isInvalid={false}
|
||||
checked={Boolean(values.switchTpShareForDs4)}
|
||||
onChange={(e) => {
|
||||
setFieldValue(
|
||||
'switchTpShareForDs4',
|
||||
e.target.checked ? 1 : 0,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{values.inputMode === PS4Mode && (
|
||||
<div className="col-sm-3">
|
||||
<Form.Select
|
||||
name="ps4ControllerType"
|
||||
className="form-select-sm"
|
||||
value={values.ps4ControllerType}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors.ps4ControllerType}
|
||||
>
|
||||
{translatedPS4ControllerTypeModes.map((o, i) => (
|
||||
<option
|
||||
key={`button-ps4ControllerType-option-${i}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
</div>
|
||||
)}
|
||||
{values.inputMode === PS4Mode && (
|
||||
<div className="mb-3">
|
||||
<Trans
|
||||
ns="SettingsPage"
|
||||
i18nKey="ps4-compatibility-label"
|
||||
>
|
||||
For <strong>PS5 compatibility</strong>, use "Arcade
|
||||
Stick" and enable PS Passthrough add-on
|
||||
<br />
|
||||
For <strong>PS4 support</strong>, use "Controller" and
|
||||
enable PS4 Mode add-on if you have the necessary files
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Form.Group>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>{t('SettingsPage:d-pad-mode-label')}</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select
|
||||
name="dpadMode"
|
||||
className="form-select-sm"
|
||||
value={values.dpadMode}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors.dpadMode}
|
||||
>
|
||||
{translatedDpadModes.map((o, i) => (
|
||||
<option
|
||||
key={`button-dpadMode-option-${i}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">{errors[o] && errors[o]?.action}</Form.Control.Feedback>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.dpadMode}
|
||||
</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
)}
|
||||
</div>
|
||||
<Form.Check
|
||||
label={t('SettingsPage:lock-hotkeys-label')}
|
||||
type="switch"
|
||||
id="LockHotkeys"
|
||||
reverse
|
||||
isInvalid={false}
|
||||
checked={Boolean(values.lockHotkeys)}
|
||||
onChange={(e) => { setFieldValue("lockHotkeys", e.target.checked ? 1 : 0); }}
|
||||
/>
|
||||
</Section>
|
||||
<Button type="submit">{t('Common:button-save-label')}</Button>
|
||||
{saveMessage ? <span className="alert">{saveMessage}</span> : null}
|
||||
<FormContext setButtonLabels={setButtonLabels}/>
|
||||
</Form>
|
||||
<Modal size="lg" show={warning.show} onHide={handleWarningClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('SettingsPage:forced-setup-mode-modal-title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className='mb-3'>
|
||||
<Trans ns="SettingsPage" i18nKey='forced-setup-mode-modal-body' components={{ strong: <strong /> }} values={{ warningCheckText: WARNING_CHECK_TEXT }} />
|
||||
</div>
|
||||
<Form.Control value={warning.acceptText} onChange={setWarningAcceptText}></Form.Control>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button disabled={warning.acceptText != WARNING_CHECK_TEXT} variant="warning" onClick={() => handleWarningClose(true, values)}>
|
||||
{t('Common:button-save-label')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => handleWarningClose(false, values, setFieldValue)}>
|
||||
{t('Common:button-dismiss-label')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
)}
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>
|
||||
{t('SettingsPage:socd-cleaning-mode-label')}
|
||||
</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select
|
||||
name="socdMode"
|
||||
className="form-select-sm"
|
||||
value={values.socdMode}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors.socdMode}
|
||||
>
|
||||
{translatedSocdModes.map((o, i) => (
|
||||
<option
|
||||
key={`button-socdMode-option-${i}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.socdMode}
|
||||
</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
<p>{t('SettingsPage:socd-cleaning-mode-note')}</p>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>
|
||||
{t('SettingsPage:forced-setup-mode-label')}
|
||||
</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Select
|
||||
name="forcedSetupMode"
|
||||
className="form-select-sm"
|
||||
value={values.forcedSetupMode}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors.forcedSetupMode}
|
||||
>
|
||||
{translatedForcedSetupModes.map((o, i) => (
|
||||
<option
|
||||
key={`button-forcedSetupMode-option-${i}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.forcedSetupMode}
|
||||
</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
<Form.Check
|
||||
label={t('SettingsPage:4-way-joystick-mode-label')}
|
||||
type="switch"
|
||||
id="fourWayMode"
|
||||
isInvalid={false}
|
||||
checked={Boolean(values.fourWayMode)}
|
||||
onChange={(e) => {
|
||||
setFieldValue('fourWayMode', e.target.checked ? 1 : 0);
|
||||
}}
|
||||
/>
|
||||
<Form.Group className="row mb-3">
|
||||
<Form.Label>
|
||||
{t('SettingsPage:profile-number-label')}
|
||||
</Form.Label>
|
||||
<div className="col-sm-3">
|
||||
<Form.Control
|
||||
type="number"
|
||||
className="row mb-3"
|
||||
value={values.profileNumber}
|
||||
min={1}
|
||||
max={4}
|
||||
isInvalid={false}
|
||||
onChange={(e) => {
|
||||
setFieldValue(
|
||||
'profileNumber',
|
||||
parseInt(e.target.value),
|
||||
);
|
||||
}}
|
||||
></Form.Control>
|
||||
</div>
|
||||
</Form.Group>
|
||||
</Section>
|
||||
<Section title={t('SettingsPage:hotkey-settings-label')}>
|
||||
<div className="mb-3">
|
||||
<Trans ns="SettingsPage" i18nKey="hotkey-settings-sub-header">
|
||||
The <strong>Fn</strong> slider provides a mappable Function
|
||||
button in the{' '}
|
||||
<NavLink exact="true" to="/pin-mapping">
|
||||
Pin Mapping
|
||||
</NavLink>{' '}
|
||||
page. By selecting the Fn slider option, the Function button
|
||||
must be held along with the selected hotkey settings.
|
||||
<br />
|
||||
Additionally, select <strong>None</strong> from the dropdown
|
||||
to unassign any button.
|
||||
</Trans>
|
||||
</div>
|
||||
{values.fnButtonPin === -1 && (
|
||||
<div className="alert alert-warning">
|
||||
{t('SettingsPage:hotkey-settings-warning')}
|
||||
</div>
|
||||
)}
|
||||
<div id="Hotkeys" hidden={values.lockHotkeys}>
|
||||
{Object.keys(hotkeyFields).map((o, i) => (
|
||||
<Form.Group key={`hotkey-${i}`} className="row mb-3">
|
||||
<div className="col-sm-auto">
|
||||
<Form.Check
|
||||
name={`${o}.auxMask`}
|
||||
label=" Fn"
|
||||
type="switch"
|
||||
className="form-select-sm"
|
||||
disabled={values.fnButtonPin === -1}
|
||||
checked={values[o] && !!values[o]?.auxMask}
|
||||
onChange={(e) => {
|
||||
setFieldValue(
|
||||
`${o}.auxMask`,
|
||||
e.target.checked ? 32768 : 0,
|
||||
);
|
||||
}}
|
||||
isInvalid={errors[o] && errors[o]?.auxMask}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors[o] && errors[o]?.action}
|
||||
</Form.Control.Feedback>
|
||||
</div>
|
||||
<span className="col-sm-auto">+</span>
|
||||
{BUTTON_MASKS.map((mask) =>
|
||||
values[o] && values[o]?.buttonsMask & mask.value ? (
|
||||
[
|
||||
<div className="col-sm-auto">
|
||||
<Form.Select
|
||||
name={`${o}.buttonsMask`}
|
||||
className="form-select-sm sm-1"
|
||||
groupClassName="mb-3"
|
||||
value={
|
||||
values[o] &&
|
||||
values[o]?.buttonsMask & mask.value
|
||||
}
|
||||
error={errors[o] && errors[o]?.buttonsMask}
|
||||
isInvalid={errors[o] && errors[o]?.buttonsMask}
|
||||
onChange={(e) => {
|
||||
setFieldValue(
|
||||
`${o}.buttonsMask`,
|
||||
(values[o] &&
|
||||
values[o]?.buttonsMask ^ mask.value) |
|
||||
e.target.value,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{BUTTON_MASKS.map((o, i2) => (
|
||||
<option
|
||||
key={`hotkey-${i}-button${i2}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
</div>,
|
||||
<span className="col-sm-auto">+</span>,
|
||||
]
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
)}
|
||||
<div className="col-sm-auto">
|
||||
<Form.Select
|
||||
name={`${o}.buttonsMask`}
|
||||
className="form-select-sm sm-1"
|
||||
groupClassName="mb-3"
|
||||
value={0}
|
||||
onChange={(e) => {
|
||||
setFieldValue(
|
||||
`${o}.buttonsMask`,
|
||||
(values[o] && values[o]?.buttonsMask) |
|
||||
e.target.value,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{BUTTON_MASKS.map((o, i2) => (
|
||||
<option
|
||||
key={`hotkey-${i}-buttonZero-${i2}`}
|
||||
value={o.value}
|
||||
>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
</div>
|
||||
<span className="col-sm-auto">=</span>
|
||||
<div className="col-sm-auto">
|
||||
<Form.Select
|
||||
name={`${o}.action`}
|
||||
className="form-select-sm"
|
||||
value={values[o] && values[o]?.action}
|
||||
onChange={handleChange}
|
||||
isInvalid={errors[o] && errors[o]?.action}
|
||||
>
|
||||
{translatedHotkeyActions.map((o, i) => (
|
||||
<option key={`hotkey-action-${i}`} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors[o] && errors[o]?.action}
|
||||
</Form.Control.Feedback>
|
||||
</div>
|
||||
</Form.Group>
|
||||
))}
|
||||
</div>
|
||||
<Form.Check
|
||||
label={t('SettingsPage:lock-hotkeys-label')}
|
||||
type="switch"
|
||||
id="LockHotkeys"
|
||||
reverse
|
||||
isInvalid={false}
|
||||
checked={Boolean(values.lockHotkeys)}
|
||||
onChange={(e) => {
|
||||
setFieldValue('lockHotkeys', e.target.checked ? 1 : 0);
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
<Button type="submit">{t('Common:button-save-label')}</Button>
|
||||
{saveMessage ? (
|
||||
<span className="alert">{saveMessage}</span>
|
||||
) : null}
|
||||
<FormContext setButtonLabels={setButtonLabels} />
|
||||
</Form>
|
||||
<Modal size="lg" show={warning.show} onHide={handleWarningClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
{t('SettingsPage:forced-setup-mode-modal-title')}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div className="mb-3">
|
||||
<Trans
|
||||
ns="SettingsPage"
|
||||
i18nKey="forced-setup-mode-modal-body"
|
||||
components={{ strong: <strong /> }}
|
||||
values={{ warningCheckText: WARNING_CHECK_TEXT }}
|
||||
/>
|
||||
</div>
|
||||
<Form.Control
|
||||
value={warning.acceptText}
|
||||
onChange={setWarningAcceptText}
|
||||
></Form.Control>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
disabled={warning.acceptText != WARNING_CHECK_TEXT}
|
||||
variant="warning"
|
||||
onClick={() => handleWarningClose(true, values)}
|
||||
>
|
||||
{t('Common:button-save-label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
handleWarningClose(false, values, setFieldValue)
|
||||
}
|
||||
>
|
||||
{t('Common:button-dismiss-label')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
const STORAGE_BUTTON_LABELS = 'buttonLabels';
|
||||
|
||||
const loadButtonLabels = () => localStorage.getItem(STORAGE_BUTTON_LABELS) ?? 'gp2040';
|
||||
const saveButtonLabels = (value) => localStorage.setItem(STORAGE_BUTTON_LABELS, value);
|
||||
const loadButtonLabels = () =>
|
||||
localStorage.getItem(STORAGE_BUTTON_LABELS) ?? 'gp2040';
|
||||
const saveButtonLabels = (value) =>
|
||||
localStorage.setItem(STORAGE_BUTTON_LABELS, value);
|
||||
|
||||
export {
|
||||
loadButtonLabels,
|
||||
saveButtonLabels,
|
||||
};
|
||||
export { loadButtonLabels, saveButtonLabels };
|
||||
|
||||
@@ -5,7 +5,7 @@ const hexToInt = (hex) => {
|
||||
|
||||
// Convert a number to hex
|
||||
const intToHex = (d) => {
|
||||
return ("0"+(Number(d).toString(16))).slice(-2).toLowerCase();
|
||||
return ('0' + Number(d).toString(16)).slice(-2).toLowerCase();
|
||||
};
|
||||
|
||||
// Convert a 32-bit ARGB value to hex format
|
||||
@@ -15,18 +15,15 @@ const rgbIntToHex = (rgbInt) => {
|
||||
let b = (rgbInt >> 0) & 255;
|
||||
|
||||
return `#${intToHex(r)}${intToHex(g)}${intToHex(b)}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Takes an array of 8-bit RGB values and returns the hex value
|
||||
const rgbArrayToHex = (values) => {
|
||||
let [r, g, b] = values;
|
||||
|
||||
if (!(r >= 0 && r <= 255))
|
||||
r = 0;
|
||||
if (!(g >= 0 && g <= 255))
|
||||
g = 0;
|
||||
if (!(b >= 0 && b <= 255))
|
||||
r = 0;
|
||||
if (!(r >= 0 && r <= 255)) r = 0;
|
||||
if (!(g >= 0 && g <= 255)) g = 0;
|
||||
if (!(b >= 0 && b <= 255)) r = 0;
|
||||
|
||||
return `#${intToHex(r)}${intToHex(g)}${intToHex(b)}`;
|
||||
};
|
||||
@@ -44,10 +41,4 @@ const rgbWheel = (pos) => {
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
hexToInt,
|
||||
intToHex,
|
||||
rgbArrayToHex,
|
||||
rgbIntToHex,
|
||||
rgbWheel,
|
||||
};
|
||||
export { hexToInt, intToHex, rgbArrayToHex, rgbIntToHex, rgbWheel };
|
||||
|
||||
@@ -1,88 +1,95 @@
|
||||
import axios from 'axios';
|
||||
import { intToHex, hexToInt, rgbIntToHex } from './Utilities';
|
||||
|
||||
const baseUrl = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:8080';
|
||||
const baseUrl =
|
||||
process.env.NODE_ENV === 'production' ? '' : 'http://localhost:8080';
|
||||
|
||||
export const baseButtonMappings = {
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
S1: { pin: -1, key: 0, error: null },
|
||||
S2: { pin: -1, key: 0, error: null },
|
||||
L3: { pin: -1, key: 0, error: null },
|
||||
R3: { pin: -1, key: 0, error: null },
|
||||
A1: { pin: -1, key: 0, error: null },
|
||||
A2: { pin: -1, key: 0, error: null },
|
||||
Fn: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
S1: { pin: -1, key: 0, error: null },
|
||||
S2: { pin: -1, key: 0, error: null },
|
||||
L3: { pin: -1, key: 0, error: null },
|
||||
R3: { pin: -1, key: 0, error: null },
|
||||
A1: { pin: -1, key: 0, error: null },
|
||||
A2: { pin: -1, key: 0, error: null },
|
||||
Fn: { pin: -1, key: 0, error: null },
|
||||
};
|
||||
|
||||
export const baseProfileOptions = {
|
||||
alternativePinMappings: [{
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
},{
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
},{
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
}]
|
||||
alternativePinMappings: [
|
||||
{
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
},
|
||||
{
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
},
|
||||
{
|
||||
Up: { pin: -1, key: 0, error: null },
|
||||
Down: { pin: -1, key: 0, error: null },
|
||||
Left: { pin: -1, key: 0, error: null },
|
||||
Right: { pin: -1, key: 0, error: null },
|
||||
B1: { pin: -1, key: 0, error: null },
|
||||
B2: { pin: -1, key: 0, error: null },
|
||||
B3: { pin: -1, key: 0, error: null },
|
||||
B4: { pin: -1, key: 0, error: null },
|
||||
L1: { pin: -1, key: 0, error: null },
|
||||
R1: { pin: -1, key: 0, error: null },
|
||||
L2: { pin: -1, key: 0, error: null },
|
||||
R2: { pin: -1, key: 0, error: null },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async function resetSettings() {
|
||||
return axios.get(`${baseUrl}/api/resetSettings`)
|
||||
return axios
|
||||
.get(`${baseUrl}/api/resetSettings`)
|
||||
.then((response) => response.data)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
async function getDisplayOptions() {
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getDisplayOptions`)
|
||||
const response = await axios.get(`${baseUrl}/api/getDisplayOptions`);
|
||||
|
||||
if (response.data.i2cAddress) {
|
||||
response.data.i2cAddress = '0x' + response.data.i2cAddress.toString(16);
|
||||
}
|
||||
response.data.splashDuration = response.data.splashDuration / 1000; // milliseconds to seconds
|
||||
response.data.displaySaverTimeout = response.data.displaySaverTimeout / 60000; // milliseconds to minutes
|
||||
response.data.displaySaverTimeout =
|
||||
response.data.displaySaverTimeout / 60000; // milliseconds to minutes
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -97,17 +104,25 @@ async function setDisplayOptions(options, isPreview) {
|
||||
newOptions.buttonLayoutRight = parseInt(options.buttonLayoutRight);
|
||||
newOptions.splashMode = parseInt(options.splashMode);
|
||||
newOptions.splashDuration = parseInt(options.splashDuration) * 1000; // seconds to milliseconds
|
||||
newOptions.displaySaverTimeout = parseInt(options.displaySaverTimeout) * 60000; // minutes to milliseconds
|
||||
newOptions.displaySaverTimeout =
|
||||
parseInt(options.displaySaverTimeout) * 60000; // minutes to milliseconds
|
||||
newOptions.splashChoice = parseInt(options.splashChoice);
|
||||
|
||||
if (newOptions.buttonLayoutCustomOptions) {
|
||||
newOptions.buttonLayoutCustomOptions.params.layout = parseInt(options.buttonLayoutCustomOptions?.params?.layout);
|
||||
newOptions.buttonLayoutCustomOptions.paramsRight.layout = parseInt(options.buttonLayoutCustomOptions?.paramsRight?.layout);
|
||||
newOptions.buttonLayoutCustomOptions.params.layout = parseInt(
|
||||
options.buttonLayoutCustomOptions?.params?.layout,
|
||||
);
|
||||
newOptions.buttonLayoutCustomOptions.paramsRight.layout = parseInt(
|
||||
options.buttonLayoutCustomOptions?.paramsRight?.layout,
|
||||
);
|
||||
}
|
||||
|
||||
delete newOptions.splashImage;
|
||||
const url = !isPreview ? `${baseUrl}/api/setDisplayOptions` : `${baseUrl}/api/setPreviewDisplayOptions`;
|
||||
return axios.post(url, newOptions)
|
||||
const url = !isPreview
|
||||
? `${baseUrl}/api/setDisplayOptions`
|
||||
: `${baseUrl}/api/setPreviewDisplayOptions`;
|
||||
return axios
|
||||
.post(url, newOptions)
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -120,26 +135,31 @@ async function setDisplayOptions(options, isPreview) {
|
||||
|
||||
async function getSplashImage() {
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getSplashImage`)
|
||||
const response = await axios.get(`${baseUrl}/api/getSplashImage`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function setSplashImage({splashImage}) {
|
||||
return axios.post(`${baseUrl}/api/setSplashImage`, {
|
||||
splashImage: btoa(String.fromCharCode.apply(null, new Uint8Array(splashImage)))
|
||||
}).then((response) => {
|
||||
return response.data;
|
||||
}).catch(console.error);
|
||||
async function setSplashImage({ splashImage }) {
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setSplashImage`, {
|
||||
splashImage: btoa(
|
||||
String.fromCharCode.apply(null, new Uint8Array(splashImage)),
|
||||
),
|
||||
})
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
async function getGamepadOptions(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getGamepadOptions`)
|
||||
const response = await axios.get(`${baseUrl}/api/getGamepadOptions`);
|
||||
setLoading(false);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -149,7 +169,8 @@ async function getGamepadOptions(setLoading) {
|
||||
}
|
||||
|
||||
async function setGamepadOptions(options) {
|
||||
return axios.post(`${baseUrl}/api/setGamepadOptions`, sanitizeRequest(options))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setGamepadOptions`, sanitizeRequest(options))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -164,10 +185,10 @@ async function getLedOptions(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getLedOptions`)
|
||||
const response = await axios.get(`${baseUrl}/api/getLedOptions`);
|
||||
setLoading(false);
|
||||
|
||||
response.data.pledColor = rgbIntToHex(response.data.pledColor) || "#ffffff";
|
||||
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;
|
||||
@@ -185,8 +206,8 @@ async function getLedOptions(setLoading) {
|
||||
async function setLedOptions(options) {
|
||||
let data = sanitizeRequest(options);
|
||||
|
||||
|
||||
return axios.post(`${baseUrl}/api/setLedOptions`, sanitizeRequest(options))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setLedOptions`, sanitizeRequest(options))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -201,14 +222,14 @@ async function getCustomTheme(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getCustomTheme`)
|
||||
const response = await axios.get(`${baseUrl}/api/getCustomTheme`);
|
||||
setLoading(false);
|
||||
|
||||
let data = { hasCustomTheme: response.data.enabled, customTheme: {} };
|
||||
|
||||
// Transform ARGB int value to hex for easy use on frontend
|
||||
Object.keys(response.data)
|
||||
.filter(p => p !== 'enabled')
|
||||
.filter((p) => p !== 'enabled')
|
||||
.forEach((button) => {
|
||||
data.customTheme[button] = {
|
||||
normal: rgbIntToHex(response.data[button].u),
|
||||
@@ -228,15 +249,15 @@ async function setCustomTheme(customThemeOptions) {
|
||||
let options = { enabled: customThemeOptions.hasCustomTheme };
|
||||
|
||||
// Transform RGB hex values to ARGB int before sending back to API
|
||||
Object.keys(customThemeOptions.customTheme)
|
||||
.forEach(p => {
|
||||
options[p] = {
|
||||
u: hexToInt(customThemeOptions.customTheme[p].normal.replace('#', '')),
|
||||
d: hexToInt(customThemeOptions.customTheme[p].pressed.replace('#', '')),
|
||||
};
|
||||
});
|
||||
Object.keys(customThemeOptions.customTheme).forEach((p) => {
|
||||
options[p] = {
|
||||
u: hexToInt(customThemeOptions.customTheme[p].normal.replace('#', '')),
|
||||
d: hexToInt(customThemeOptions.customTheme[p].pressed.replace('#', '')),
|
||||
};
|
||||
});
|
||||
|
||||
return axios.post(`${baseUrl}/api/setCustomTheme`, sanitizeRequest(options))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setCustomTheme`, sanitizeRequest(options))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -251,7 +272,7 @@ async function getPinMappings(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getPinMappings`)
|
||||
const response = await axios.get(`${baseUrl}/api/getPinMappings`);
|
||||
let mappings = { ...baseButtonMappings };
|
||||
for (let prop of Object.keys(response.data))
|
||||
mappings[prop].pin = parseInt(response.data[prop]);
|
||||
@@ -265,9 +286,12 @@ async function getPinMappings(setLoading) {
|
||||
|
||||
async function setPinMappings(mappings) {
|
||||
let data = {};
|
||||
Object.keys(mappings).map((button, i) => data[button] = mappings[button].pin);
|
||||
Object.keys(mappings).map(
|
||||
(button, i) => (data[button] = mappings[button].pin),
|
||||
);
|
||||
|
||||
return axios.post(`${baseUrl}/api/setPinMappings`, sanitizeRequest(data))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setPinMappings`, sanitizeRequest(data))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -287,7 +311,7 @@ async function getProfileOptions(setLoading) {
|
||||
response.data['alternativePinMappings'].forEach((altButtons, index) => {
|
||||
for (let prop of Object.keys(altButtons))
|
||||
profileOptions['alternativePinMappings'][index][prop].pin = parseInt(
|
||||
response.data['alternativePinMappings'][index][prop]
|
||||
response.data['alternativePinMappings'][index][prop],
|
||||
);
|
||||
});
|
||||
setLoading(false);
|
||||
@@ -303,11 +327,14 @@ async function setProfileOptions(options) {
|
||||
data['alternativePinMappings'] = [];
|
||||
options['alternativePinMappings'].forEach((altButtons, index) => {
|
||||
let altMapping = {};
|
||||
Object.keys(options['alternativePinMappings'][index]).map((button, i) => altMapping[button] = altButtons[button].pin);
|
||||
Object.keys(options['alternativePinMappings'][index]).map(
|
||||
(button, i) => (altMapping[button] = altButtons[button].pin),
|
||||
);
|
||||
data['alternativePinMappings'].push(altMapping);
|
||||
});
|
||||
|
||||
return axios.post(`${baseUrl}/api/setProfileOptions`, sanitizeRequest(data))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setProfileOptions`, sanitizeRequest(data))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -322,7 +349,7 @@ async function getKeyMappings(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getKeyMappings`)
|
||||
const response = await axios.get(`${baseUrl}/api/getKeyMappings`);
|
||||
setLoading(false);
|
||||
|
||||
let mappings = { ...baseButtonMappings };
|
||||
@@ -338,9 +365,12 @@ async function getKeyMappings(setLoading) {
|
||||
|
||||
async function setKeyMappings(mappings) {
|
||||
let data = {};
|
||||
Object.keys(mappings).map((button, i) => data[button] = mappings[button].key);
|
||||
Object.keys(mappings).map(
|
||||
(button, i) => (data[button] = mappings[button].key),
|
||||
);
|
||||
|
||||
return axios.post(`${baseUrl}/api/setKeyMappings`, sanitizeRequest(data))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setKeyMappings`, sanitizeRequest(data))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -355,7 +385,7 @@ async function getAddonsOptions(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getAddonsOptions`)
|
||||
const response = await axios.get(`${baseUrl}/api/getAddonsOptions`);
|
||||
const data = response.data;
|
||||
setLoading(false);
|
||||
|
||||
@@ -373,11 +403,14 @@ async function getAddonsOptions(setLoading) {
|
||||
async function setAddonsOptions(options) {
|
||||
if (options.keyboardHostMap) {
|
||||
let data = {};
|
||||
Object.keys(options.keyboardHostMap).map((button, i) => data[button] = options.keyboardHostMap[button].key);
|
||||
Object.keys(options.keyboardHostMap).map(
|
||||
(button, i) => (data[button] = options.keyboardHostMap[button].key),
|
||||
);
|
||||
options.keyboardHostMap = data;
|
||||
}
|
||||
|
||||
return axios.post(`${baseUrl}/api/setAddonsOptions`, sanitizeRequest(options))
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setAddonsOptions`, sanitizeRequest(options))
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -389,7 +422,8 @@ async function setAddonsOptions(options) {
|
||||
}
|
||||
|
||||
async function setPS4Options(options) {
|
||||
return axios.post(`${baseUrl}/api/setPS4Options`, options)
|
||||
return axios
|
||||
.post(`${baseUrl}/api/setPS4Options`, options)
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
return true;
|
||||
@@ -404,7 +438,7 @@ async function getFirmwareVersion(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getFirmwareVersion`)
|
||||
const response = await axios.get(`${baseUrl}/api/getFirmwareVersion`);
|
||||
setLoading(false);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -417,7 +451,7 @@ async function getMemoryReport(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getMemoryReport`)
|
||||
const response = await axios.get(`${baseUrl}/api/getMemoryReport`);
|
||||
setLoading(false);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -426,12 +460,11 @@ async function getMemoryReport(setLoading) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function getUsedPins(setLoading) {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${baseUrl}/api/getUsedPins`)
|
||||
const response = await axios.get(`${baseUrl}/api/getUsedPins`);
|
||||
setLoading(false);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -441,13 +474,14 @@ async function getUsedPins(setLoading) {
|
||||
}
|
||||
|
||||
async function reboot(bootMode) {
|
||||
return axios.post(`${baseUrl}/api/reboot`, { bootMode })
|
||||
return axios
|
||||
.post(`${baseUrl}/api/reboot`, { bootMode })
|
||||
.then((response) => response.data)
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function sanitizeRequest(request) {
|
||||
const newRequest = {...request};
|
||||
const newRequest = { ...request };
|
||||
delete newRequest.pledIndex1;
|
||||
delete newRequest.pledIndex2;
|
||||
delete newRequest.pledIndex3;
|
||||
@@ -480,7 +514,7 @@ const WebApi = {
|
||||
getFirmwareVersion,
|
||||
getMemoryReport,
|
||||
getUsedPins,
|
||||
reboot
|
||||
reboot,
|
||||
};
|
||||
|
||||
export default WebApi;
|
||||
|
||||
@@ -14,9 +14,9 @@ i18n
|
||||
escapeValue: true,
|
||||
},
|
||||
detection: {
|
||||
order: ['localStorage', 'navigator']
|
||||
order: ['localStorage', 'navigator'],
|
||||
},
|
||||
resources: {en, 'en-GB': enGB}
|
||||
resources: { en, 'en-GB': enGB },
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
import './i18n';
|
||||
import "./index.scss";
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
import './i18n';
|
||||
import './index.scss';
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
$primary: #495057;
|
||||
$danger: #7b2d26;
|
||||
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
|
||||
:root {
|
||||
--bs-primary-rgb: 20, 23, 30;
|
||||
@@ -14,8 +14,8 @@ $danger: #7b2d26;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@@ -27,7 +27,8 @@ h1 {
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
@@ -72,7 +73,10 @@ button {
|
||||
|
||||
.cover {
|
||||
position: fixed;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user