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:
Pelsin
2023-08-16 18:42:42 +02:00
committed by GitHub
parent bd4984c582
commit b44df76f50
66 changed files with 4776 additions and 2632 deletions

View File

@@ -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
View File

@@ -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",

View File

@@ -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"

View File

@@ -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);
});

View File

@@ -11,4 +11,4 @@
"_postman_variable_scope": "environment",
"_postman_exported_at": "2021-11-09T21:30:14.170Z",
"_postman_exported_using": "Postman/9.1.3"
}
}

View File

@@ -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": [""]
}
}
]
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -20,7 +20,7 @@
}
&.selected {
background-color: #DEDEDE;
background-color: #dedede;
}
}
@@ -36,4 +36,4 @@
.sketch-picker {
margin-top: 10px;
}
}
}

View File

@@ -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}

View File

@@ -6,7 +6,8 @@ const DangerSection = ({ className, titleClassName, ...props }) => {
<Section
className={`border-danger ${className}`}
titleClassName={`text-white bg-danger ${titleClassName}`}
{...props} />
{...props}
/>
);
};

View File

@@ -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>
);

View File

@@ -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 (

View File

@@ -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>

View File

@@ -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;

View File

@@ -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}`)}

View File

@@ -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>

View File

@@ -3,6 +3,7 @@
margin: -5px 8px 0 12px;
}
.nav-link, .nav-item * {
.nav-link,
.nav-item * {
font-size: 0.875rem;
}

View File

@@ -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>
);
};

View File

@@ -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}

View File

@@ -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 },
];

View File

@@ -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)
];

View File

@@ -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 },

View File

@@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
const CircleHalf = () => (
<svg

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
const MoonStars = () => (
<svg

View File

@@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
const Sun = () => (
<svg

View File

@@ -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',
};

View File

@@ -1,6 +1,6 @@
export default {
"language-selector": {
"en-GB": "English",
"en": "American English",
'language-selector': {
'en-GB': 'English',
en: 'American English',
},
};

View File

@@ -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>",
};

View File

@@ -1,5 +1,5 @@
export default {
"player": {
"pled-color-label": "RGB PLED Colour",
player: {
'pled-color-label': 'RGB PLED Colour',
},
};

View File

@@ -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)',

View File

@@ -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',
};

View File

@@ -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...',
};

View File

@@ -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",
}
};

View File

@@ -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',
};

View File

@@ -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',

View File

@@ -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',
};

View File

@@ -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.',
};

View File

@@ -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",
}
};

View File

@@ -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',
};

View File

@@ -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',
},
};

View File

@@ -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!',
};

View File

@@ -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?',
};

View File

@@ -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

View File

@@ -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>

View File

@@ -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&apos;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&apos;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;

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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&apos;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&apos;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>

View File

@@ -31,4 +31,3 @@ table.pin-mapping-table {
margin-right: 10px;
}
}

View File

@@ -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>&nbsp;&nbsp;{String(buttonMappings['Up'].pin).padStart(2)}<br />
{String(buttonMappings['Left'].pin).padStart(2)}&nbsp;&nbsp;{String(buttonMappings['Right'].pin).padStart(2)}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{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 />
&nbsp;&nbsp;{String(buttonMappings['Down'].pin).padStart(2)}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{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>
&nbsp;&nbsp;{String(buttonMappings['Up'].pin).padStart(2)}
<br />
{String(buttonMappings['Left'].pin).padStart(2)}&nbsp;&nbsp;
{String(buttonMappings['Right'].pin).padStart(2)}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
{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 />
&nbsp;&nbsp;{String(buttonMappings['Down'].pin).padStart(2)}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
{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>

View File

@@ -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>
);
}

View File

@@ -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="&nbsp;&nbsp;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="&nbsp;&nbsp;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>
);
}

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 />);

View File

@@ -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;
}
}