Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -43,6 +44,8 @@ function App() {
document.body.style = `background: ${GetTheme(themeId).backgroundColor};`;
}, [themeId]);

useGamepad();

return (
<>
<DndProvider
Expand Down
17 changes: 15 additions & 2 deletions src/constants/enums.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
const WorkflowItemTypes = Object.freeze({
export const WorkflowItemTypes = Object.freeze({
HEAT_ON: "heatOn",
SET_LED_BRIGHTNESS: "setLEDbrightness",
WAIT: "wait",
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
ALL_OFF: "allOff", // turns off fan and heat
});

export default WorkflowItemTypes;
export const GamepadButtons = Object.freeze({
CROSS_A: 0,
CIRCLE_B: 1,
SQUARE_X: 2,
UP_DPAD: 12,
DOWN_DPAD: 13,
L1_LB: 4,
R1_RB: 5
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect } from "react";
import { setCurrentStepEllapsedTimeInSeconds } from "../../workflowEditor/workflowSlice";
import Container from "react-bootstrap/Container";
import PrideText, { PrideTextWithDiv } from "../../../themes/PrideText";
import WorkflowItemTypes from "../../../constants/enums";
import { WorkflowItemTypes } from "../../../constants/enums";
import { DEGREE_SYMBOL } from "../../../constants/temperature";
import { convertToFahrenheitFromCelsius } from "../../../services/utils";
import { useRef } from "react";
Expand Down
25 changes: 24 additions & 1 deletion src/features/deviceInteraction/FanOn/FanOn.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
import React from "react";
import React, { useEffect, useMemo } from "react";
import { PrideTextWithDiv } from "../../../themes/PrideText";
import ToggleSwitch from "../../shared/styledComponents/Switch";
import { useSelector } from "react-redux";

export default React.forwardRef((props, ref) => {
const enterKeyCode = 13;
const startTime = useMemo(() => Date.now(), []);
const leftButton = useSelector((state) => state.gamepad.squareIsPressed.current);
const cancelButton = useSelector((state) => state.gamepad.circleIsPressed.current);

useEffect(() => {
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();
}
// 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) {
ref.current.click();
Expand Down
24 changes: 23 additions & 1 deletion src/features/deviceInteraction/HeatOn/HeatOn.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { useRef } from "react";
import { useRef, useEffect, useMemo } from "react";
import ToggleSwitch from "../../../features/shared/styledComponents/Switch";
import { PrideTextWithDiv } from "../../../themes/PrideText";
import { useSelector } from "react-redux";

export default function HeatOn(props) {
const ref = useRef(null);
const enterKeyCode = 13;
const startTime = useMemo(() => Date.now(), []);
const bottomButton = useSelector((state) => state.gamepad.crossIsPressed.current);
const cancelButton = useSelector((state) => state.gamepad.circleIsPressed.current);

useEffect(() => {
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();
}
// 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) {
ref.current.click();
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -32,9 +32,44 @@ export default function WriteTemperatureContainer() {
);

const dispatch = useDispatch();
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(() => {
const characteristic = getCharacteristic(writeTemperatureUuid);
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 (upButton) {
const handleIncrement = onClickIncrement(1);
handleIncrement();
return;
}
if (maxButton) {
const maxTemp = temperatureControlValues[1];
const handleClick = onClick(maxTemp);
handleClick();
return;
}
if (minButton) {
const minTemp = temperatureControlValues[0];
const handleClick = onClick(minTemp);
handleClick();
return;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [downButton, upButton, maxButton, minButton]);

useEffect(() => {
const characteristic = getCharacteristic(writeTemperatureUuid);
function handleTargetTemperatureChanged(event) {
const targetTemperature =
convertCurrentTemperatureCharacteristicToCelcius(event.target.value);
Expand Down
64 changes: 64 additions & 0 deletions src/features/deviceInteraction/gamepadSlice.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/features/workflowEditor/CreateWorkflowItemButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/features/workflowEditor/WorkflowButtons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion src/features/workflowEditor/WorkflowItemEditor.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import WorkflowItemTypes from "../../../constants/enums";
import { WorkflowItemTypes } from "../../../constants/enums";
import {
convertToCelsiusFromFahrenheit,
isValueInValidVolcanoCelciusRange,
Expand Down
96 changes: 96 additions & 0 deletions src/services/gamepad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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';

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() {
const dispatch = useDispatch();

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, 50); // Poll every 50ms

return () => clearInterval(intervalId);
}
2 changes: 1 addition & 1 deletion src/services/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/store.js
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -10,5 +11,6 @@ export default configureStore({
deviceInteraction: deviceInteractionReducer,
settings: settingsReducer,
workflow: workflowReducer,
gamepad: gamepadReducer,
},
});