diff --git a/headers/gamepad.h b/headers/gamepad.h index a3a0c6d83..77d12420c 100644 --- a/headers/gamepad.h +++ b/headers/gamepad.h @@ -4,6 +4,7 @@ #include "BoardConfig.h" #include "types.h" #include +#include #include "enums.pb.h" #include "gamepad/GamepadState.h" diff --git a/headers/storagemanager.h b/headers/storagemanager.h index a8908b54e..40dbb1397 100644 --- a/headers/storagemanager.h +++ b/headers/storagemanager.h @@ -68,6 +68,7 @@ class Storage { void nextProfile(); void previousProfile(); void setFunctionalPinMappings(); + char* currentProfileLabel(); void ResetSettings(); // EEPROM Reset Feature diff --git a/proto/config.proto b/proto/config.proto index f4b7726a1..f802b00fa 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -192,6 +192,7 @@ message GpioMappingInfo message GpioMappings { repeated GpioMappingInfo pins = 1 [(nanopb).max_count = 30]; + optional string profileLabel = 2 [(nanopb).max_length = 16]; } diff --git a/src/configs/webconfig.cpp b/src/configs/webconfig.cpp index f1d5e8643..8de0abdfb 100644 --- a/src/configs/webconfig.cpp +++ b/src/configs/webconfig.cpp @@ -560,6 +560,10 @@ std::string setProfileOptions() profileOptions.gpioMappingsSets[altsIndex].pins[pin].customDpadMask = (uint32_t)alt[pinName]["customDpadMask"]; } } + size_t profileLabelSize = sizeof(profileOptions.gpioMappingsSets[altsIndex].profileLabel); + strncpy(profileOptions.gpioMappingsSets[altsIndex].profileLabel, alt["profileLabel"], profileLabelSize - 1); + profileOptions.gpioMappingsSets[altsIndex].profileLabel[profileLabelSize - 1] = '\0'; + profileOptions.gpioMappingsSets_count = ++altsIndex; if (altsIndex > 2) break; } @@ -614,6 +618,7 @@ std::string getProfileOptions() writePinDoc(i, "pin27", profileOptions.gpioMappingsSets[i].pins[27]); writePinDoc(i, "pin28", profileOptions.gpioMappingsSets[i].pins[28]); writePinDoc(i, "pin29", profileOptions.gpioMappingsSets[i].pins[29]); + writeDoc(doc, "alternativePinMappings", i, "profileLabel", profileOptions.gpioMappingsSets[i].profileLabel); } return serialize_json(doc); @@ -1039,21 +1044,24 @@ std::string setPinMappings() { DynamicJsonDocument doc = get_post_data(); - GpioMappingInfo* gpioMappings = Storage::getInstance().getGpioMappings().pins; + GpioMappings& gpioMappings = Storage::getInstance().getGpioMappings(); char pinName[6]; for (Pin_t pin = 0; pin < (Pin_t)NUM_BANK0_GPIOS; pin++) { snprintf(pinName, 6, "pin%0*d", 2, pin); // setting a pin shouldn't change a new existing addon/reserved pin - if (gpioMappings[pin].action != GpioAction::RESERVED && - gpioMappings[pin].action != GpioAction::ASSIGNED_TO_ADDON && + if (gpioMappings.pins[pin].action != GpioAction::RESERVED && + gpioMappings.pins[pin].action != GpioAction::ASSIGNED_TO_ADDON && (GpioAction)doc[pinName]["action"] != GpioAction::RESERVED && (GpioAction)doc[pinName]["action"] != GpioAction::ASSIGNED_TO_ADDON) { - gpioMappings[pin].action = (GpioAction)doc[pinName]["action"]; - gpioMappings[pin].customButtonMask = (uint32_t)doc[pinName]["customButtonMask"]; - gpioMappings[pin].customDpadMask = (uint32_t)doc[pinName]["customDpadMask"]; + gpioMappings.pins[pin].action = (GpioAction)doc[pinName]["action"]; + gpioMappings.pins[pin].customButtonMask = (uint32_t)doc[pinName]["customButtonMask"]; + gpioMappings.pins[pin].customDpadMask = (uint32_t)doc[pinName]["customDpadMask"]; } } + size_t profileLabelSize = sizeof(gpioMappings.profileLabel); + strncpy(gpioMappings.profileLabel, doc["profileLabel"], profileLabelSize - 1); + gpioMappings.profileLabel[profileLabelSize - 1] = '\0'; Storage::getInstance().save(); @@ -1064,7 +1072,7 @@ std::string getPinMappings() { DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); - GpioMappingInfo* gpioMappings = Storage::getInstance().getGpioMappings().pins; + GpioMappings& gpioMappings = Storage::getInstance().getGpioMappings(); const auto writePinDoc = [&](const char* key, const GpioMappingInfo& value) -> void { @@ -1073,36 +1081,38 @@ std::string getPinMappings() writeDoc(doc, key, "customDpadMask", value.customDpadMask); }; - writePinDoc("pin00", gpioMappings[0]); - writePinDoc("pin01", gpioMappings[1]); - writePinDoc("pin02", gpioMappings[2]); - writePinDoc("pin03", gpioMappings[3]); - writePinDoc("pin04", gpioMappings[4]); - writePinDoc("pin05", gpioMappings[5]); - writePinDoc("pin06", gpioMappings[6]); - writePinDoc("pin07", gpioMappings[7]); - writePinDoc("pin08", gpioMappings[8]); - writePinDoc("pin09", gpioMappings[9]); - writePinDoc("pin10", gpioMappings[10]); - writePinDoc("pin11", gpioMappings[11]); - writePinDoc("pin12", gpioMappings[12]); - writePinDoc("pin13", gpioMappings[13]); - writePinDoc("pin14", gpioMappings[14]); - writePinDoc("pin15", gpioMappings[15]); - writePinDoc("pin16", gpioMappings[16]); - writePinDoc("pin17", gpioMappings[17]); - writePinDoc("pin18", gpioMappings[18]); - writePinDoc("pin19", gpioMappings[19]); - writePinDoc("pin20", gpioMappings[20]); - writePinDoc("pin21", gpioMappings[21]); - writePinDoc("pin22", gpioMappings[22]); - writePinDoc("pin23", gpioMappings[23]); - writePinDoc("pin24", gpioMappings[24]); - writePinDoc("pin25", gpioMappings[25]); - writePinDoc("pin26", gpioMappings[26]); - writePinDoc("pin27", gpioMappings[27]); - writePinDoc("pin28", gpioMappings[28]); - writePinDoc("pin29", gpioMappings[29]); + writePinDoc("pin00", gpioMappings.pins[0]); + writePinDoc("pin01", gpioMappings.pins[1]); + writePinDoc("pin02", gpioMappings.pins[2]); + writePinDoc("pin03", gpioMappings.pins[3]); + writePinDoc("pin04", gpioMappings.pins[4]); + writePinDoc("pin05", gpioMappings.pins[5]); + writePinDoc("pin06", gpioMappings.pins[6]); + writePinDoc("pin07", gpioMappings.pins[7]); + writePinDoc("pin08", gpioMappings.pins[8]); + writePinDoc("pin09", gpioMappings.pins[9]); + writePinDoc("pin10", gpioMappings.pins[10]); + writePinDoc("pin11", gpioMappings.pins[11]); + writePinDoc("pin12", gpioMappings.pins[12]); + writePinDoc("pin13", gpioMappings.pins[13]); + writePinDoc("pin14", gpioMappings.pins[14]); + writePinDoc("pin15", gpioMappings.pins[15]); + writePinDoc("pin16", gpioMappings.pins[16]); + writePinDoc("pin17", gpioMappings.pins[17]); + writePinDoc("pin18", gpioMappings.pins[18]); + writePinDoc("pin19", gpioMappings.pins[19]); + writePinDoc("pin20", gpioMappings.pins[20]); + writePinDoc("pin21", gpioMappings.pins[21]); + writePinDoc("pin22", gpioMappings.pins[22]); + writePinDoc("pin23", gpioMappings.pins[23]); + writePinDoc("pin24", gpioMappings.pins[24]); + writePinDoc("pin25", gpioMappings.pins[25]); + writePinDoc("pin26", gpioMappings.pins[26]); + writePinDoc("pin27", gpioMappings.pins[27]); + writePinDoc("pin28", gpioMappings.pins[28]); + writePinDoc("pin29", gpioMappings.pins[29]); + + writeDoc(doc, "profileLabel", gpioMappings.profileLabel); return serialize_json(doc); } diff --git a/src/display/ui/screens/ButtonLayoutScreen.cpp b/src/display/ui/screens/ButtonLayoutScreen.cpp index e3a9b6e4f..62e86cfaf 100644 --- a/src/display/ui/screens/ButtonLayoutScreen.cpp +++ b/src/display/ui/screens/ButtonLayoutScreen.cpp @@ -115,13 +115,19 @@ int8_t ButtonLayoutScreen::update() { void ButtonLayoutScreen::generateHeader() { // Limit to 21 chars with 6x8 font for now statusBar.clear(); + Storage& storage = Storage::getInstance(); // Display Profile # banner if ( profileModeDisplay ) { if (((getMillis() - profileDelayStart) / 1000) < profileDelay) { - statusBar = " Profile #"; - statusBar += std::to_string(getGamepad()->getOptions().profileNumber); - return; + statusBar.assign(storage.currentProfileLabel(), strlen(storage.currentProfileLabel())); + if (statusBar.empty()) { + statusBar = " Profile #"; + statusBar += std::to_string(getGamepad()->getOptions().profileNumber); + } else { + statusBar.insert(statusBar.begin(), (21-statusBar.length())/2, ' '); + } + return; } else { profileModeDisplay = false; } @@ -172,7 +178,7 @@ void ButtonLayoutScreen::generateHeader() { case INPUT_MODE_CONFIG: statusBar += "CONFIG"; break; } - const TurboOptions& turboOptions = Storage::getInstance().getAddonOptions().turboOptions; + const TurboOptions& turboOptions = storage.getAddonOptions().turboOptions; if ( turboOptions.enabled ) { statusBar += " T"; if ( turboOptions.shotCount < 10 ) // padding diff --git a/src/storagemanager.cpp b/src/storagemanager.cpp index 3eb0c25a2..069ec25ea 100644 --- a/src/storagemanager.cpp +++ b/src/storagemanager.cpp @@ -131,6 +131,16 @@ void Storage::previousProfile() this->config.gamepadOptions.profileNumber -= 1; } +/** + * @brief Return the current profile label. + */ +char* Storage::currentProfileLabel() { + if (this->config.gamepadOptions.profileNumber == 1) + return this->config.gpioMappings.profileLabel; + else + return this->config.profileOptions.gpioMappingsSets[config.gamepadOptions.profileNumber-2].profileLabel; +} + void Storage::setFunctionalPinMappings() { GpioMappingInfo* alts = nullptr; diff --git a/www/package-lock.json b/www/package-lock.json index 46608c9fb..34f13ce52 100644 --- a/www/package-lock.json +++ b/www/package-lock.json @@ -24,7 +24,7 @@ "react-router-dom": "^6.10.0", "react-select": "^5.7.5", "yup": "^1.1.1", - "zustand": "^4.4.1" + "zustand": "^4.5.5" }, "devDependencies": { "@types/lodash": "^4.17.1", @@ -5747,9 +5747,10 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -6017,18 +6018,19 @@ } }, "node_modules/zustand": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.1.tgz", - "integrity": "sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "license": "MIT", "dependencies": { - "use-sync-external-store": "1.2.0" + "use-sync-external-store": "1.2.2" }, "engines": { "node": ">=12.7.0" }, "peerDependencies": { "@types/react": ">=16.8", - "immer": ">=9.0", + "immer": ">=9.0.6", "react": ">=16.8" }, "peerDependenciesMeta": { diff --git a/www/package.json b/www/package.json index 9384163d3..f5a30ee9b 100644 --- a/www/package.json +++ b/www/package.json @@ -20,7 +20,7 @@ "react-router-dom": "^6.10.0", "react-select": "^5.7.5", "yup": "^1.1.1", - "zustand": "^4.4.1" + "zustand": "^4.5.5" }, "scripts": { "analyze": "npx vite-bundle-visualizer", diff --git a/www/server/app.js b/www/server/app.js index 71e0f3898..94a40af69 100644 --- a/www/server/app.js +++ b/www/server/app.js @@ -16,13 +16,20 @@ const __dirname = path.dirname(__filename); const { pico: picoController } = JSON.parse( readFileSync(path.resolve(__dirname, '../src/Data/Controllers.json'), 'utf8'), ); -const PinMappings = Object.entries(picoController).reduce( - (acc, [key, value]) => ({ - ...acc, - [key]: { action: value, customButtonMask: 0, customDpadMask: 0 }, - }), - {}, -); + +// Structure pin mappings to include masks and profile label +const createPinMappings = ({ profileLabel = 'Profile' }) => { + let pinMappings = { profileLabel }; + + for (const [key, value] of Object.entries(picoController)) { + pinMappings[key] = { + action: value, + customButtonMask: 0, + customDpadMask: 0, + }; + } + return pinMappings; +}; const port = process.env.PORT || 8080; @@ -260,7 +267,7 @@ app.get('/api/getCustomTheme', (req, res) => { }); app.get('/api/getPinMappings', (req, res) => { - return res.send(PinMappings); + return res.send(createPinMappings({ profileLabel: 'Profile 1' })); }); app.get('/api/getKeyMappings', (req, res) => @@ -380,7 +387,11 @@ app.get('/api/getWiiControls', (req, res) => app.get('/api/getProfileOptions', (req, res) => { return res.send({ - alternativePinMappings: [PinMappings, PinMappings, PinMappings], + alternativePinMappings: [ + createPinMappings({ profileLabel: 'Profile 2' }), + createPinMappings({ profileLabel: 'Profile 3' }), + createPinMappings({ profileLabel: 'Profile 4' }), + ], }); }); diff --git a/www/src/Locales/en/PinMapping.jsx b/www/src/Locales/en/PinMapping.jsx index 2d27bdb69..bd8450d93 100644 --- a/www/src/Locales/en/PinMapping.jsx +++ b/www/src/Locales/en/PinMapping.jsx @@ -6,12 +6,12 @@ export default { 'pin-viewer': 'Pin viewer', 'pin-pressed': 'Pressed pin: {{pressedPin}}', 'pin-header-label': 'Pin', - 'profile-text-1': 'Base(Profile 1)', - 'profile-text-2': 'Profile 2', - 'profile-text-3': 'Profile 3', - 'profile-text-4': 'Profile 4', - 'profile-pin-mapping-title-base': 'Base(Profile 1) - Pin Mapping', - 'profile-pin-mapping-title': 'Profile {{profileNumber}} - Pin Mapping', + + 'profile-label-title': 'Profile name', + 'profile-label-description': + 'Max 16 characters. Letters, numbers, and spaces allowed.', + 'profile-pin-mapping-title': '{{profileLabel}} - Pin Mapping', + 'profile-label-default': 'Profile {{profileNumber}}', 'profile-pins-warning': 'Try to avoid changing the buttons and/or directions used for the switch profile hotkeys. Otherwise, it will be difficult to understand what profile is being selected!', 'profile-copy-base': 'Copy base profile', diff --git a/www/src/Locales/ja-JP/PinMapping.jsx b/www/src/Locales/ja-JP/PinMapping.jsx index ccf802472..a4724f419 100644 --- a/www/src/Locales/ja-JP/PinMapping.jsx +++ b/www/src/Locales/ja-JP/PinMapping.jsx @@ -5,12 +5,8 @@ export default { '未接続や未実装の端子を割当設定するとコントローラが動作しない状態に陥る可能性があります。無効な設定を初期化したい場合は<2>設定初期化のページで初期化を実行してください。', 'pin-viewer': '端子確認', 'pin-pressed': 'このボタンの配線先は {{pressedPin}} 番端子です!', - 'profile-text-1': '基本(プロファイル 1)', - 'profile-text-2': 'プロファイル 2', - 'profile-text-3': 'プロファイル 3', - 'profile-text-4': 'プロファイル 4', - 'profile-pin-mapping-title-base': 'デフォルト(プロファイル 1) - 端子割当', - 'profile-pin-mapping-title': 'プロファイル {{profileNumber}} - 端子割当', + 'profile-pin-mapping-title': '{{profileLabel}} - 端子割当', + 'profile-label-default': 'プロファイル {{profileNumber}}', 'pin-header-label': '端子', 'profile-pins-warning': 'プロファイルの変更ホットキーに設定しているボタンや方向キーの設定変更は、現在のプロファイル選択状況がわからなくなる原因となるため非推奨です。', diff --git a/www/src/Locales/zh-CN/PinMapping.jsx b/www/src/Locales/zh-CN/PinMapping.jsx index 05348a101..031fc8d5f 100644 --- a/www/src/Locales/zh-CN/PinMapping.jsx +++ b/www/src/Locales/zh-CN/PinMapping.jsx @@ -7,12 +7,8 @@ export default { 'pin-viewer': '引脚查看器', 'pin-pressed': '按下的引脚:{{pressedPin}}', 'pin-header-label': '引脚', - 'profile-text-1': '基础(档案 1)', - 'profile-text-2': '档案 2', - 'profile-text-3': '档案 3', - 'profile-text-4': '档案 4', - 'profile-pin-mapping-title-base': '基础(档案 1) - 引脚映射', - 'profile-pin-mapping-title': '档案 {{profileNumber}} - 引脚映射', + 'profile-pin-mapping-title': '{{profileLabel}} - 引脚映射', + 'profile-label-default': '档案 {{profileNumber}}', 'profile-pins-warning': '尽量避免修改已设置为切换档案快捷键的按键或方向键,否则之后将很难理解你选择的档案配置!', 'profile-copy-base': '复制基础档案', diff --git a/www/src/Pages/PinMapping.tsx b/www/src/Pages/PinMapping.tsx index 654084f6f..3b088090a 100644 --- a/www/src/Pages/PinMapping.tsx +++ b/www/src/Pages/PinMapping.tsx @@ -1,12 +1,18 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { + memo, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; import { NavLink } from 'react-router-dom'; +import { useShallow } from 'zustand/react/shallow'; import { Alert, Button, Col, Form, Nav, Row, Tab } from 'react-bootstrap'; import { Trans, useTranslation } from 'react-i18next'; import invert from 'lodash/invert'; import omit from 'lodash/omit'; import { AppContext } from '../Contexts/AppContext'; -import usePinStore, { MaskPayload, SetPinType } from '../Store/usePinStore'; import useProfilesStore from '../Store/useProfilesStore'; import Section from '../Components/Section'; @@ -26,14 +32,6 @@ type OptionType = { customDpadMask: number; }; -type PinSectionType = { - sectionTitle: string; - pins: { [key: string]: MaskPayload }; - saveHandler: () => Promise; - setHandler: SetPinType; - copyHandler?: () => void; -}; - const disabledOptions = [ BUTTON_ACTIONS.RESERVED, BUTTON_ACTIONS.ASSIGNED_TO_ADDON, @@ -100,18 +98,54 @@ const getMultiValue = (pinData) => { ) : options.filter((option) => option.value === pinData.action); }; - -const PinSection = ({ - sectionTitle, - pins, - saveHandler, - setHandler, - copyHandler, -}: PinSectionType) => { - const { buttonLabels, updateUsedPins } = useContext(AppContext); +const ProfileLabel = memo(function ProfileLabel({ + profileIndex, +}: { + profileIndex: number; +}) { const { t } = useTranslation(''); - const [saveMessage, setSaveMessage] = useState(''); + const setProfileLabel = useProfilesStore((state) => state.setProfileLabel); + const profileLabel = useProfilesStore( + (state) => state.profiles[profileIndex].profileLabel, + ); + const onLabelChange = useCallback( + (event) => + setProfileLabel( + profileIndex, + event.target.value.replace(/[^a-zA-Z0-9\s]/g, ''), + ), + [], + ); + return ( +
+ {t('PinMapping:profile-label-title')} + + {t('PinMapping:profile-label-description')} +
+ ); +}); + +const PinSelectList = memo(function PinSelectList({ + profileIndex, +}: { + profileIndex: number; +}) { + const setProfilePin = useProfilesStore((state) => state.setProfilePin); + const pins = useProfilesStore( + useShallow((state) => omit(state.profiles[profileIndex], 'profileLabel')), + ); + const { t } = useTranslation(''); + const { buttonLabels } = useContext(AppContext); const { buttonLabelType, swapTpShareLabels } = buttonLabels; const CURRENT_BUTTONS = getButtonLabels(buttonLabelType, swapTpShareLabels); const buttonNames = omit(CURRENT_BUTTONS, ['label', 'value']); @@ -121,7 +155,7 @@ const PinSection = ({ (selected: MultiValue | SingleValue) => { // Handle clearing if (!selected || (Array.isArray(selected) && !selected.length)) { - setHandler(pin, { + setProfilePin(profileIndex, pin, { action: BUTTON_ACTIONS.NONE, customButtonMask: 0, customDpadMask: 0, @@ -130,13 +164,14 @@ const PinSection = ({ const lastSelected = selected[selected.length - 1]; // Revert to single option if choosing action type if (lastSelected.type === 'action') { - setHandler(pin, { + setProfilePin(profileIndex, pin, { action: lastSelected.value, customButtonMask: 0, customDpadMask: 0, }); } else { - setHandler( + setProfilePin( + profileIndex, pin, selected.reduce( (masks, option) => ({ @@ -159,7 +194,7 @@ const PinSection = ({ ); } } else { - setHandler(pin, { + setProfilePin(profileIndex, pin, { action: selected[0].value, customButtonMask: 0, customDpadMask: 0, @@ -180,12 +215,50 @@ const PinSection = ({ }, [buttonNames], ); + return Object.entries(pins).map(([pin, pinData], index) => ( +
+
+ +
+ +
+ )); +}); + +const PinSection = memo(function PinSection({ + profileIndex, +}: { + profileIndex: number; +}) { + const { t } = useTranslation(''); + const copyBaseProfile = useProfilesStore((state) => state.copyBaseProfile); + const setProfilePin = useProfilesStore((state) => state.setProfilePin); + const saveProfiles = useProfilesStore((state) => state.saveProfiles); + const profileLabel = + useProfilesStore((state) => state.profiles[profileIndex].profileLabel) || + t('PinMapping:profile-label-default', { + profileNumber: profileIndex + 1, + }); + const { updateUsedPins, buttonLabels } = useContext(AppContext); + const { buttonLabelType, swapTpShareLabels } = buttonLabels; + const CURRENT_BUTTONS = getButtonLabels(buttonLabelType, swapTpShareLabels); + const buttonNames = omit(CURRENT_BUTTONS, ['label', 'value']); + + const [saveMessage, setSaveMessage] = useState(''); const handleSubmit = useCallback(async (e) => { e.preventDefault(); e.stopPropagation(); try { - await saveHandler(); + await saveProfiles(); updateUsedPins(); setSaveMessage(t('Common:saved-success-message')); } catch (error) { @@ -206,34 +279,23 @@ const PinSection = ({
{t(`PinMapping:profile-pins-warning`)} -
+
-
- {Object.entries(pins).map(([pin, pinData], index) => ( -
-
- -
- -
- ))} + +
+
+
- setHandler( + setProfilePin( + profileIndex, // Convert getHeldPins format to setPinMappings format pin < 10 ? `pin0${pin}` : `pin${pin}`, { @@ -250,8 +312,8 @@ const PinSection = ({ ) } /> - {copyHandler && ( - )} @@ -262,45 +324,33 @@ const PinSection = ({
); -}; +}); export default function PinMapping() { - const { fetchPins, pins, setPin, savePins } = usePinStore(); - const { fetchProfiles, profiles, setProfile, setProfilePin, saveProfiles } = - useProfilesStore(); + const fetchProfiles = useProfilesStore((state) => state.fetchProfiles); + const profiles = useProfilesStore((state) => state.profiles); const [pressedPin, setPressedPin] = useState(null); const { t } = useTranslation(''); useEffect(() => { - fetchPins(); fetchProfiles(); }, []); return ( - +

{t('PinMapping:sub-header-text')}

@@ -319,35 +369,18 @@ export default function PinMapping() { + + + - + + + + + + + - - {profiles.map((profilePins, profileIndex) => ( - - - setProfilePin(profileIndex, pin, maskPayload) - } - copyHandler={() => { - setProfile(profileIndex, pins); - }} - /> - - ))}
diff --git a/www/src/Store/usePinStore.ts b/www/src/Store/usePinStore.ts deleted file mode 100644 index 3e29c3566..000000000 --- a/www/src/Store/usePinStore.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { create } from 'zustand'; - -import WebApi from '../Services/WebApi'; -import { BUTTON_ACTIONS, PinActionValues } from '../Data/Pins'; - -type CustomMasks = { - customButtonMask: number; - customDpadMask: number; -}; - -export type MaskPayload = { - action: PinActionValues; -} & CustomMasks; - -type State = { - pins: { [key: string]: MaskPayload }; - loadingPins: boolean; -}; - -export type SetPinType = ( - pin: string, - { action, customButtonMask, customDpadMask }: MaskPayload, -) => void; - -type Actions = { - fetchPins: () => void; - setPin: SetPinType; - savePins: () => Promise; -}; - -const DEFAULT_PIN_STATE = { - action: BUTTON_ACTIONS.NONE, - customButtonMask: 0, - customDpadMask: 0, -}; - -const INITIAL_STATE: State = { - pins: { - pin00: DEFAULT_PIN_STATE, - pin01: DEFAULT_PIN_STATE, - pin02: DEFAULT_PIN_STATE, - pin03: DEFAULT_PIN_STATE, - pin04: DEFAULT_PIN_STATE, - pin05: DEFAULT_PIN_STATE, - pin06: DEFAULT_PIN_STATE, - pin07: DEFAULT_PIN_STATE, - pin08: DEFAULT_PIN_STATE, - pin09: DEFAULT_PIN_STATE, - pin10: DEFAULT_PIN_STATE, - pin11: DEFAULT_PIN_STATE, - pin12: DEFAULT_PIN_STATE, - pin13: DEFAULT_PIN_STATE, - pin14: DEFAULT_PIN_STATE, - pin15: DEFAULT_PIN_STATE, - pin16: DEFAULT_PIN_STATE, - pin17: DEFAULT_PIN_STATE, - pin18: DEFAULT_PIN_STATE, - pin19: DEFAULT_PIN_STATE, - pin20: DEFAULT_PIN_STATE, - pin21: DEFAULT_PIN_STATE, - pin22: DEFAULT_PIN_STATE, - pin23: DEFAULT_PIN_STATE, - pin24: DEFAULT_PIN_STATE, - pin25: DEFAULT_PIN_STATE, - pin26: DEFAULT_PIN_STATE, - pin27: DEFAULT_PIN_STATE, - pin28: DEFAULT_PIN_STATE, - pin29: DEFAULT_PIN_STATE, - }, - loadingPins: false, -}; - -const usePinStore = create()((set, get) => ({ - ...INITIAL_STATE, - fetchPins: async () => { - set({ loadingPins: true }); - const pins = await WebApi.getPinMappings(); - set((state) => ({ - ...state, - pins, - loadingPins: false, - })); - }, - - setPin: (pin, { action, customButtonMask = 0, customDpadMask = 0 }) => - set((state) => ({ - ...state, - pins: { - ...state.pins, - [pin]: { - action, - customButtonMask, - customDpadMask, - }, - }, - })), - savePins: async () => WebApi.setPinMappings(get().pins), -})); - -export default usePinStore; diff --git a/www/src/Store/useProfilesStore.ts b/www/src/Store/useProfilesStore.ts index 9a448b5a4..a666ef80c 100644 --- a/www/src/Store/useProfilesStore.ts +++ b/www/src/Store/useProfilesStore.ts @@ -11,10 +11,42 @@ export type MaskPayload = { action: PinActionValues; } & CustomMasks; -type ProfilePinType = { [key: string]: MaskPayload }; +export type PinsType = { + pin00: MaskPayload; + pin01: MaskPayload; + pin02: MaskPayload; + pin03: MaskPayload; + pin04: MaskPayload; + pin05: MaskPayload; + pin06: MaskPayload; + pin07: MaskPayload; + pin08: MaskPayload; + pin09: MaskPayload; + pin10: MaskPayload; + pin11: MaskPayload; + pin12: MaskPayload; + pin13: MaskPayload; + pin14: MaskPayload; + pin15: MaskPayload; + pin16: MaskPayload; + pin17: MaskPayload; + pin18: MaskPayload; + pin19: MaskPayload; + pin20: MaskPayload; + pin21: MaskPayload; + pin22: MaskPayload; + pin23: MaskPayload; + pin24: MaskPayload; + pin25: MaskPayload; + pin26: MaskPayload; + pin27: MaskPayload; + pin28: MaskPayload; + pin29: MaskPayload; + profileLabel: string; +}; type State = { - profiles: ProfilePinType[]; + profiles: PinsType[]; loadingProfiles: boolean; }; @@ -27,8 +59,9 @@ export type SetProfilePinType = ( type Actions = { fetchProfiles: () => void; setProfilePin: SetProfilePinType; + copyBaseProfile: (profileIndex: number) => void; + setProfileLabel: (profileIndex: number, profileLabel: string) => void; saveProfiles: () => Promise; - setProfile: (profileIndex: number, pins: ProfilePinType) => void; }; const DEFAULT_PIN_STATE = { @@ -37,7 +70,7 @@ const DEFAULT_PIN_STATE = { customDpadMask: 0, }; -const defaultProfilePins: ProfilePinType = { +const defaultProfilePins: PinsType = { pin00: DEFAULT_PIN_STATE, pin01: DEFAULT_PIN_STATE, pin02: DEFAULT_PIN_STATE, @@ -68,10 +101,16 @@ const defaultProfilePins: ProfilePinType = { pin27: DEFAULT_PIN_STATE, pin28: DEFAULT_PIN_STATE, pin29: DEFAULT_PIN_STATE, + profileLabel: '', }; const INITIAL_STATE: State = { - profiles: [defaultProfilePins, defaultProfilePins, defaultProfilePins], + profiles: [ + defaultProfilePins, + defaultProfilePins, + defaultProfilePins, + defaultProfilePins, + ], loadingProfiles: false, }; @@ -79,41 +118,60 @@ const useProfilesStore = create()((set, get) => ({ ...INITIAL_STATE, fetchProfiles: async () => { set({ loadingProfiles: true }); + + // TODO, unify baseProfile with other profiles when done in web api + const baseProfile = await WebApi.getPinMappings(); const profiles = await WebApi.getProfileOptions(); + set((state) => ({ ...state, - profiles, + profiles: [baseProfile, ...profiles], loadingProfiles: false, })); }, - setProfile: (profileIndex, pins) => - set((state) => ({ - ...state, - profiles: state.profiles.map((profile, index) => - index === profileIndex ? { ...profile, ...pins } : profile, - ), - })), - setProfilePin: ( - profileIndex, - pin, - { action, customButtonMask = 0, customDpadMask = 0 }, - ) => + copyBaseProfile: (profileIndex) => set((state) => ({ ...state, profiles: state.profiles.map((profile, index) => index === profileIndex ? { ...profile, - [pin]: { - action, - customButtonMask, - customDpadMask, - }, + ...state.profiles[0], + profileLabel: profile.profileLabel, } : profile, ), })), - saveProfiles: async () => WebApi.setProfileOptions(get().profiles), + setProfilePin: ( + profileIndex, + pin, + { action, customButtonMask = 0, customDpadMask = 0 }, + ) => + set((state) => { + const profiles = [...state.profiles]; + profiles[profileIndex] = { + ...profiles[profileIndex], + [pin]: { + action, + customButtonMask, + customDpadMask, + }, + }; + return { profiles }; + }), + setProfileLabel: (profileIndex, profileLabel) => + set((state) => { + const profiles = [...state.profiles]; + profiles[profileIndex] = { ...profiles[profileIndex], profileLabel }; + return { profiles }; + }), + saveProfiles: async () => { + const [baseProfile, ...profiles] = get().profiles; + return Promise.all([ + WebApi.setPinMappings(baseProfile), + WebApi.setProfileOptions(profiles), + ]); + }, })); export default useProfilesStore;