From 5bebfb84e9dfd64cd01d2c9bbed8acc5123ed141 Mon Sep 17 00:00:00 2001 From: Scott Sautter Date: Fri, 30 Aug 2024 09:20:48 -0400 Subject: [PATCH 1/2] adds gamepad functionality --- src/constants/enums.js | 4 +++ .../deviceInteraction/FanOn/FanOn.jsx | 13 +++++++- .../deviceInteraction/HeatOn/HeatOn.jsx | 13 +++++++- .../WriteTemperatureContainer.jsx | 30 +++++++++++++++++++ src/services/gamepad.js | 30 +++++++++++++++++++ 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/services/gamepad.js diff --git a/src/constants/enums.js b/src/constants/enums.js index 6807b1f..dbafddb 100644 --- a/src/constants/enums.js +++ b/src/constants/enums.js @@ -5,6 +5,10 @@ const WorkflowItemTypes = Object.freeze({ HEAT_OFF: "heatOff", FAN_ON: "fanOn", FAN_ON_GLOBAL: "fanOnGlobal", + TEMP_UP: "tempUp", + TEMP_DOWN: "tempDown", + TEMP_MIN: "tempMin", // lower temp in config + TEMP_MAX: "tempMax" // upper temp in config }); export default WorkflowItemTypes; diff --git a/src/features/deviceInteraction/FanOn/FanOn.jsx b/src/features/deviceInteraction/FanOn/FanOn.jsx index d3c5693..a1f9dde 100644 --- a/src/features/deviceInteraction/FanOn/FanOn.jsx +++ b/src/features/deviceInteraction/FanOn/FanOn.jsx @@ -1,8 +1,19 @@ -import React from "react"; +import React, {useEffect} from "react"; import { PrideTextWithDiv } from "../../../themes/PrideText"; import ToggleSwitch from "../../shared/styledComponents/Switch"; +import useGamepad from '../../../services/gamepad'; +import WorkflowItemTypes from '../../../constants/enums'; + export default React.forwardRef((props, ref) => { const enterKeyCode = 13; + const buttonIsPressed = useGamepad(WorkflowItemTypes.FAN_ON); + + useEffect(() => { + if (buttonIsPressed) { + ref.current.click(); + } + }, [buttonIsPressed, ref]); + const handler = (e) => { if (e.keyCode === enterKeyCode) { ref.current.click(); diff --git a/src/features/deviceInteraction/HeatOn/HeatOn.jsx b/src/features/deviceInteraction/HeatOn/HeatOn.jsx index e5b7aec..e1af851 100644 --- a/src/features/deviceInteraction/HeatOn/HeatOn.jsx +++ b/src/features/deviceInteraction/HeatOn/HeatOn.jsx @@ -1,9 +1,20 @@ -import { useRef } from "react"; +import { useRef, useEffect } from "react"; import ToggleSwitch from "../../../features/shared/styledComponents/Switch"; import { PrideTextWithDiv } from "../../../themes/PrideText"; +import useGamepad from '../../../services/gamepad'; +import WorkflowItemTypes from '../../../constants/enums'; + export default function HeatOn(props) { const ref = useRef(null); const enterKeyCode = 13; + const buttonIsPressed = useGamepad(WorkflowItemTypes.HEAT_ON); + + useEffect(() => { + if (buttonIsPressed) { + ref.current.click(); + } + }, [buttonIsPressed, ref]); + const handler = (e) => { if (e.keyCode === enterKeyCode) { ref.current.click(); diff --git a/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx b/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx index 7fae8bf..2ebefd7 100644 --- a/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx +++ b/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx @@ -19,6 +19,8 @@ import { getDisplayTemperature } from "../../../services/utils"; import PrideText from "../../../themes/PrideText"; import store from "../../../store"; +import useGamepad from '../../../services/gamepad'; +import WorkflowItemTypes from '../../../constants/enums'; export default function WriteTemperatureContainer() { const targetTemperature = useSelector( @@ -32,6 +34,34 @@ export default function WriteTemperatureContainer() { ); const dispatch = useDispatch(); + const downButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_DOWN); + const upButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_UP); + const minButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_MIN); + const maxButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_MAX); + + useEffect(() => { + console.log('change') + if (downButtonIsPressed) { + const handleIncrement = onClickIncrement(-1); + handleIncrement(); + } + if (upButtonIsPressed) { + const handleIncrement = onClickIncrement(1); + handleIncrement(); + } + if (maxButtonIsPressed) { + const maxTemp = temperatureControlValues[1]; + const handleClick = onClick(maxTemp); + handleClick(); + } + if (minButtonIsPressed) { + const minTemp = temperatureControlValues[0]; + const handleClick = onClick(minTemp); + handleClick(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [downButtonIsPressed, upButtonIsPressed, maxButtonIsPressed, minButtonIsPressed]); + useEffect(() => { const characteristic = getCharacteristic(writeTemperatureUuid); diff --git a/src/services/gamepad.js b/src/services/gamepad.js new file mode 100644 index 0000000..495fa57 --- /dev/null +++ b/src/services/gamepad.js @@ -0,0 +1,30 @@ +import { useState, useEffect } from "react"; +import WorkflowItemTypes from "../constants/enums"; + +// PlayStation/Xbox/Nintendo +const buttonIdxMapping = new Map([ + [WorkflowItemTypes.HEAT_ON, 0], // Cross/A/B + [WorkflowItemTypes.FAN_ON, 2], // Square/X/Y + [WorkflowItemTypes.TEMP_UP, 12], // Up on D-Pad + [WorkflowItemTypes.TEMP_DOWN, 13], // Down on D-Pad + [WorkflowItemTypes.TEMP_MIN, 4], // R1/RB/R + [WorkflowItemTypes.TEMP_MAX, 5], // L1/LB/L +]) + +export default function useGamepad(wfItemType) { + const [gamepad, setGamepad] = useState(null); + const buttonIdx = buttonIdxMapping.get(wfItemType); + + useEffect(() => { + const handleGamepadInput = () => { + const pads = navigator.getGamepads().filter(p => p !== null); + setGamepad(pads[pads.length - 1]); // Use the last connected gamepad + }; + + const intervalId = setInterval(handleGamepadInput, 100); // Poll every 100ms + return () => clearInterval(intervalId); + }, []); + + const isButtonPressed = gamepad?.buttons[buttonIdx]?.pressed; + return isButtonPressed; +} \ No newline at end of file From a7f16fc7946778de1dd8ed63fc660ef52f23bdbd Mon Sep 17 00:00:00 2001 From: Scott Sautter Date: Tue, 3 Sep 2024 13:34:42 -0400 Subject: [PATCH 2/2] redux gamepad --- src/App.js | 3 + src/constants/enums.js | 15 ++- .../CurrentWorkflowExecutionDisplay.jsx | 2 +- .../deviceInteraction/FanOn/FanOn.jsx | 24 +++- .../deviceInteraction/HeatOn/HeatOn.jsx | 23 +++- .../WriteTemperatureContainer.jsx | 35 +++--- .../deviceInteraction/gamepadSlice.js | 64 ++++++++++ .../CreateWorkflowItemButton.jsx | 2 +- .../workflowEditor/WorkflowButtons.jsx | 2 +- .../workflowEditor/WorkflowItemEditor.jsx | 2 +- .../shared/WorkflowItemValidator.js | 2 +- src/services/gamepad.js | 116 ++++++++++++++---- src/services/utils.js | 2 +- src/store.js | 2 + 14 files changed, 233 insertions(+), 61 deletions(-) create mode 100644 src/features/deviceInteraction/gamepadSlice.js diff --git a/src/App.js b/src/App.js index 7adafa1..712c91e 100644 --- a/src/App.js +++ b/src/App.js @@ -19,6 +19,7 @@ import { TouchBackend } from "react-dnd-touch-backend"; import Snowfall from "./features/shared/Snowfall"; import { isMobile } from "./constants/constants"; import DragPreview from "./features/workflowEditor/DND/DragPreview"; +import useGamepad from "./services/gamepad"; const Div = styled.div` color: ${(props) => props.theme.primaryFontColor}; background-color: ${(props) => props.theme.backgroundColor}; @@ -43,6 +44,8 @@ function App() { document.body.style = `background: ${GetTheme(themeId).backgroundColor};`; }, [themeId]); + useGamepad(); + return ( <> { const enterKeyCode = 13; - const buttonIsPressed = useGamepad(WorkflowItemTypes.FAN_ON); + const startTime = useMemo(() => Date.now(), []); + const leftButton = useSelector((state) => state.gamepad.squareIsPressed.current); + const cancelButton = useSelector((state) => state.gamepad.circleIsPressed.current); useEffect(() => { - if (buttonIsPressed) { + const currentTime = Date.now(); + const timeElapsed = currentTime - startTime; + // check to see if half a second has lapsed since mount + if (timeElapsed >= 500) { ref.current.click(); } - }, [buttonIsPressed, ref]); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, [leftButton]); + + +useEffect(() => { + if (!cancelButton || !props.isFanOn) return; + ref.current.click(); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, [cancelButton]) const handler = (e) => { if (e.keyCode === enterKeyCode) { diff --git a/src/features/deviceInteraction/HeatOn/HeatOn.jsx b/src/features/deviceInteraction/HeatOn/HeatOn.jsx index e1af851..8b834b8 100644 --- a/src/features/deviceInteraction/HeatOn/HeatOn.jsx +++ b/src/features/deviceInteraction/HeatOn/HeatOn.jsx @@ -1,19 +1,30 @@ -import { useRef, useEffect } from "react"; +import { useRef, useEffect, useMemo } from "react"; import ToggleSwitch from "../../../features/shared/styledComponents/Switch"; import { PrideTextWithDiv } from "../../../themes/PrideText"; -import useGamepad from '../../../services/gamepad'; -import WorkflowItemTypes from '../../../constants/enums'; +import { useSelector } from "react-redux"; export default function HeatOn(props) { const ref = useRef(null); const enterKeyCode = 13; - const buttonIsPressed = useGamepad(WorkflowItemTypes.HEAT_ON); + const startTime = useMemo(() => Date.now(), []); + const bottomButton = useSelector((state) => state.gamepad.crossIsPressed.current); + const cancelButton = useSelector((state) => state.gamepad.circleIsPressed.current); useEffect(() => { - if (buttonIsPressed) { + const currentTime = Date.now(); + const timeElapsed = currentTime - startTime; + // check to see if half a second has lapsed since mount + if (timeElapsed >= 500) { ref.current.click(); } - }, [buttonIsPressed, ref]); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, [bottomButton]); + +useEffect(() => { + if (!cancelButton || !props.isHeatOn) return; + ref.current.click(); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, [cancelButton]) const handler = (e) => { if (e.keyCode === enterKeyCode) { diff --git a/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx b/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx index 2ebefd7..553a8ee 100644 --- a/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx +++ b/src/features/deviceInteraction/WriteTemperature/WriteTemperatureContainer.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useMemo } from "react"; import { getCharacteristic } from "../../../services/BleCharacteristicCache"; import { writeTemperatureUuid, heatOnUuid } from "../../../constants/uuids"; import { @@ -19,8 +19,6 @@ import { getDisplayTemperature } from "../../../services/utils"; import PrideText from "../../../themes/PrideText"; import store from "../../../store"; -import useGamepad from '../../../services/gamepad'; -import WorkflowItemTypes from '../../../constants/enums'; export default function WriteTemperatureContainer() { const targetTemperature = useSelector( @@ -34,37 +32,44 @@ export default function WriteTemperatureContainer() { ); const dispatch = useDispatch(); - const downButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_DOWN); - const upButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_UP); - const minButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_MIN); - const maxButtonIsPressed = useGamepad(WorkflowItemTypes.TEMP_MAX); - + const startTime = useMemo(() => Date.now(), []); + const upButton = useSelector((state) => state.gamepad.upIsPressed.current); + const downButton = useSelector((state) => state.gamepad.downIsPressed.current); + const minButton = useSelector((state) => state.gamepad.l1IsPressed.current); + const maxButton = useSelector((state) => state.gamepad.r1IsPressed.current); + useEffect(() => { - console.log('change') - if (downButtonIsPressed) { + const currentTime = Date.now(); + const timeElapsed = currentTime - startTime; + // check to see if half a second has lapsed since mount + if (timeElapsed < 500) return; + if (downButton) { const handleIncrement = onClickIncrement(-1); handleIncrement(); + return; } - if (upButtonIsPressed) { + if (upButton) { const handleIncrement = onClickIncrement(1); handleIncrement(); + return; } - if (maxButtonIsPressed) { + if (maxButton) { const maxTemp = temperatureControlValues[1]; const handleClick = onClick(maxTemp); handleClick(); + return; } - if (minButtonIsPressed) { + if (minButton) { const minTemp = temperatureControlValues[0]; const handleClick = onClick(minTemp); handleClick(); + return; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [downButtonIsPressed, upButtonIsPressed, maxButtonIsPressed, minButtonIsPressed]); + }, [downButton, upButton, maxButton, minButton]); useEffect(() => { const characteristic = getCharacteristic(writeTemperatureUuid); - function handleTargetTemperatureChanged(event) { const targetTemperature = convertCurrentTemperatureCharacteristicToCelcius(event.target.value); diff --git a/src/features/deviceInteraction/gamepadSlice.js b/src/features/deviceInteraction/gamepadSlice.js new file mode 100644 index 0000000..029b27f --- /dev/null +++ b/src/features/deviceInteraction/gamepadSlice.js @@ -0,0 +1,64 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { RE_INITIALIZE_STORE } from "../../constants/actions"; + +export const gamepadSlice = createSlice({ + name: "gamepadController", + initialState: { + crossIsPressed: {previous: false, current: false}, + squareIsPressed: {previous: false, current: false}, + circleIsPressed: {previous: false, current: false}, + r1IsPressed: {previous: false, current: false}, + l1IsPressed: {previous: false, current: false}, + upIsPressed: {previous: false, current: false}, + downIsPressed: {previous: false, current: false}, + }, + reducers: { + setCrossIsPressed: (state, action) => { + state.crossIsPressed = action.payload; + }, + setSquareIsPressed: (state, action) => { + state.squareIsPressed = action.payload; + }, + setCircleIsPressed: (state, action) => { + state.circleIsPressed = action.payload; + }, + setR1IsPressed: (state, action) => { + state.r1IsPressed = action.payload; + }, + setL1IsPressed: (state, action) => { + state.l1IsPressed = action.payload; + }, + setUpIsPressed: (state, action) => { + state.upIsPressed = action.payload; + }, + setDownIsPressed: (state, action) => { + state.downIsPressed = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(RE_INITIALIZE_STORE, () => { + return { + crossIsPressed: {previous: false, current: false}, + squareIsPressed: {previous: false, current: false}, + circleIsPressed: {previous: false, current: false}, + r1IsPressed: {previous: false, current: false}, + l1IsPressed: {previous: false, current: false}, + upIsPressed: {previous: false, current: false}, + downIsPressed: {previous: false, current: false}, + }; + }); + }, +}); + +// Action creators are generated for each case reducer function +export const { + setCrossIsPressed, + setSquareIsPressed, + setCircleIsPressed, + setR1IsPressed, + setL1IsPressed, + setUpIsPressed, + setDownIsPressed, +} = gamepadSlice.actions; + +export default gamepadSlice.reducer; diff --git a/src/features/workflowEditor/CreateWorkflowItemButton.jsx b/src/features/workflowEditor/CreateWorkflowItemButton.jsx index 790ba59..152e16a 100644 --- a/src/features/workflowEditor/CreateWorkflowItemButton.jsx +++ b/src/features/workflowEditor/CreateWorkflowItemButton.jsx @@ -3,7 +3,7 @@ import { WriteNewConfigToLocalStorage } from "../../services/utils"; import Button from "./shared/WorkflowFooterButtons"; import cloneDeep from "lodash/cloneDeep"; import { setCurrentWorkflows } from "../settings/settingsSlice"; -import WorkflowItemTypes from "../../constants/enums"; +import { WorkflowItemTypes } from "../../constants/enums"; import PrideText from "../../themes/PrideText"; export default function CreateWorkflowItemButton(props) { diff --git a/src/features/workflowEditor/WorkflowButtons.jsx b/src/features/workflowEditor/WorkflowButtons.jsx index 1700f89..7e67f7d 100644 --- a/src/features/workflowEditor/WorkflowButtons.jsx +++ b/src/features/workflowEditor/WorkflowButtons.jsx @@ -22,7 +22,7 @@ import { setIsHeatOn, setTargetTemperature, } from "../deviceInteraction/deviceInteractionSlice"; -import WorkflowItemTypes from "../../constants/enums"; +import { WorkflowItemTypes } from "../../constants/enums"; import { getCharacteristic } from "../../services/BleCharacteristicCache"; import { useDispatch, useSelector } from "react-redux"; import { setLEDbrightness } from "../settings/settingsSlice"; diff --git a/src/features/workflowEditor/WorkflowItemEditor.jsx b/src/features/workflowEditor/WorkflowItemEditor.jsx index b6ea1d5..16adee6 100644 --- a/src/features/workflowEditor/WorkflowItemEditor.jsx +++ b/src/features/workflowEditor/WorkflowItemEditor.jsx @@ -1,7 +1,7 @@ import Select from "react-bootstrap/FormSelect"; import Label from "react-bootstrap/FormLabel"; import Control from "react-bootstrap/FormControl"; -import WorkflowItemTypes from "../../constants/enums"; +import { WorkflowItemTypes } from "../../constants/enums"; import { WriteNewConfigToLocalStorage } from "../../services/utils"; import cloneDeep from "lodash/cloneDeep"; import styled from "styled-components"; diff --git a/src/features/workflowEditor/shared/WorkflowItemValidator.js b/src/features/workflowEditor/shared/WorkflowItemValidator.js index 26d5998..d0f5056 100644 --- a/src/features/workflowEditor/shared/WorkflowItemValidator.js +++ b/src/features/workflowEditor/shared/WorkflowItemValidator.js @@ -1,4 +1,4 @@ -import WorkflowItemTypes from "../../../constants/enums"; +import { WorkflowItemTypes } from "../../../constants/enums"; import { convertToCelsiusFromFahrenheit, isValueInValidVolcanoCelciusRange, diff --git a/src/services/gamepad.js b/src/services/gamepad.js index 495fa57..82ede6c 100644 --- a/src/services/gamepad.js +++ b/src/services/gamepad.js @@ -1,30 +1,96 @@ -import { useState, useEffect } from "react"; -import WorkflowItemTypes from "../constants/enums"; +import { GamepadButtons } from "../constants/enums"; +import { useDispatch } from "react-redux"; +import { setCrossIsPressed, + setSquareIsPressed, + setCircleIsPressed, + setR1IsPressed, + setL1IsPressed, + setUpIsPressed, + setDownIsPressed } from "../features/deviceInteraction/gamepadSlice"; +import store from '../store'; -// PlayStation/Xbox/Nintendo -const buttonIdxMapping = new Map([ - [WorkflowItemTypes.HEAT_ON, 0], // Cross/A/B - [WorkflowItemTypes.FAN_ON, 2], // Square/X/Y - [WorkflowItemTypes.TEMP_UP, 12], // Up on D-Pad - [WorkflowItemTypes.TEMP_DOWN, 13], // Down on D-Pad - [WorkflowItemTypes.TEMP_MIN, 4], // R1/RB/R - [WorkflowItemTypes.TEMP_MAX, 5], // L1/LB/L -]) +const buttonsToCheck = [ + { + buttonIdx: GamepadButtons.CROSS_A, + dispatcher: setCrossIsPressed, + toggle: true, + }, + { + buttonIdx: GamepadButtons.SQUARE_X, + dispatcher: setSquareIsPressed, + toggle: true, + }, + { + buttonIdx: GamepadButtons.CIRCLE_B, + dispatcher: setCircleIsPressed, + }, + { + buttonIdx: GamepadButtons.R1_RB, + dispatcher: setR1IsPressed, + }, + { + buttonIdx: GamepadButtons.L1_LB, + dispatcher: setL1IsPressed, + }, + { + buttonIdx: GamepadButtons.UP_DPAD, + dispatcher: setUpIsPressed, + }, + { + buttonIdx: GamepadButtons.DOWN_DPAD, + dispatcher: setDownIsPressed, + }, +] -export default function useGamepad(wfItemType) { - const [gamepad, setGamepad] = useState(null); - const buttonIdx = buttonIdxMapping.get(wfItemType); +export default function useGamepad() { + const dispatch = useDispatch(); - useEffect(() => { - const handleGamepadInput = () => { - const pads = navigator.getGamepads().filter(p => p !== null); - setGamepad(pads[pads.length - 1]); // Use the last connected gamepad - }; + const handleGamepadInput = () => { + const pads = navigator.getGamepads().filter(p => p !== null); + if (!pads.length) return; + const storePad = store.getState().gamepad; + buttonsToCheck.forEach(({buttonIdx, dispatcher, toggle}) => { + let storeValue = false; + const gamepadValue = pads.some((p) => p.buttons[buttonIdx].pressed); + switch (buttonIdx) { + case GamepadButtons.CROSS_A: + storeValue = storePad.crossIsPressed; + break; + case GamepadButtons.SQUARE_X: + storeValue = storePad.squareIsPressed; + break; + case GamepadButtons.CIRCLE_B: + storeValue = storePad.circleIsPressed; + break; + case GamepadButtons.R1_RB: + storeValue = storePad.r1IsPressed; + break; + case GamepadButtons.L1_LB: + storeValue = storePad.l1IsPressed; + break; + case GamepadButtons.UP_DPAD: + storeValue = storePad.upIsPressed; + break; + case GamepadButtons.DOWN_DPAD: + storeValue = storePad.downIsPressed; + break; + default: + storeValue = false; + break; + } + if (storeValue.previous !== gamepadValue) { + if (toggle) { + dispatch(dispatcher({previous: gamepadValue, current: !storeValue.previous ? !storeValue.current : storeValue.current})); + } else { + dispatch(dispatcher({previous: gamepadValue, current: gamepadValue})); + } + } else { + dispatch(dispatcher({...storeValue, previous: gamepadValue})); + } + }) + }; - const intervalId = setInterval(handleGamepadInput, 100); // Poll every 100ms - return () => clearInterval(intervalId); - }, []); - - const isButtonPressed = gamepad?.buttons[buttonIdx]?.pressed; - return isButtonPressed; + const intervalId = setInterval(handleGamepadInput, 50); // Poll every 50ms + + return () => clearInterval(intervalId); } \ No newline at end of file diff --git a/src/services/utils.js b/src/services/utils.js index 651dbd2..6bdc534 100644 --- a/src/services/utils.js +++ b/src/services/utils.js @@ -4,7 +4,7 @@ import { MAX_CELSIUS_TEMP, DEGREE_SYMBOL, } from "../constants/temperature"; -import WorkflowItemTypes from "../constants/enums"; +import { WorkflowItemTypes } from "../constants/enums"; import { localStorageKey, diff --git a/src/store.js b/src/store.js index 5385234..d4143a3 100644 --- a/src/store.js +++ b/src/store.js @@ -1,6 +1,7 @@ import { configureStore } from "@reduxjs/toolkit"; import deviceInformationReducer from "./features/deviceInformation/deviceInformationSlice"; import deviceInteractionReducer from "./features/deviceInteraction/deviceInteractionSlice"; +import gamepadReducer from "./features/deviceInteraction/gamepadSlice"; import settingsReducer from "./features/settings/settingsSlice"; import workflowReducer from "./features/workflowEditor/workflowSlice"; @@ -10,5 +11,6 @@ export default configureStore({ deviceInteraction: deviceInteractionReducer, settings: settingsReducer, workflow: workflowReducer, + gamepad: gamepadReducer, }, });