From 7920d22948061af0b6ccb388b3e7ee0771e7e76e Mon Sep 17 00:00:00 2001 From: hello-neranti Date: Fri, 12 Jul 2024 15:43:05 -0700 Subject: [PATCH 1/9] First keyboard controller commit --- src/pages/operator/tsx/create_component.md | 2 +- .../tsx/default_layouts/SIMPLE_LAYOUT.tsx | 5 + .../KeyboardFunctionProvider.tsx | 139 ++++++++++++++++++ src/pages/operator/tsx/index.tsx | 2 + .../tsx/layout_components/CameraView.tsx | 6 +- .../tsx/layout_components/KeyboardControl.tsx | 17 +++ .../tsx/utils/component_definitions.tsx | 6 + 7 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx create mode 100644 src/pages/operator/tsx/layout_components/KeyboardControl.tsx diff --git a/src/pages/operator/tsx/create_component.md b/src/pages/operator/tsx/create_component.md index 0b542630..78dfb7ba 100644 --- a/src/pages/operator/tsx/create_component.md +++ b/src/pages/operator/tsx/create_component.md @@ -5,7 +5,7 @@ To create a new component you'll want to follow these steps: 1. **Create a new type** for your component in `ComponentType` in `utils/component_definitions.tsx`. - If there are going to be subtypes of your component then define an id for each of the subtypes like `CameraViewId` in `utils/component_definitions.tsx` - - If you component needs any other field in order for it to render (such as a `TabDefinition` having a `label`), then create a separate definition for your component with those fields. + - If your component needs any other field in order for it to render (such as a `TabDefinition` having a `label`), then create a separate definition for your component with those fields. 1. **Create a new file** in `layout_components` with the React code for your new component. The React functional component should take `CustomizableComponentProps` as its props. A field in `CustomizableComponentProps` is the `ComponentDefinition`, so you should be able to access all of the fields in the components definition there. Here are some more details about the React component you create: diff --git a/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx b/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx index a0e74b6f..4109d728 100644 --- a/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx +++ b/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx @@ -9,6 +9,7 @@ import { LayoutDefinition, ActionMode, LayoutGridDefinition, + KeyboardControlDefinition, } from "../utils/component_definitions"; /** @@ -43,6 +44,10 @@ export const BASIC_LAYOUT: LayoutDefinition = { displayButtons: true, children: [], } as CameraViewDefinition, + { + //KEYBOARD CONTROL + type: ComponentType.KeyboardControl, + } as KeyboardControlDefinition, ], }, ], diff --git a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx new file mode 100644 index 00000000..0d4f2a26 --- /dev/null +++ b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx @@ -0,0 +1,139 @@ +import React from "react"; + +enum Mode { + Base = "base", + Arm = "arm", + Wrist = "wrist", +} + +export default function KeyboardFunctionProvider() { + const [mode, setMode] = React.useState(); + + const handleKeyPress = React.useCallback( + (event) => { + switch (event.key) { + case "w": + HandleW(); + break; + case "a": + HandleA(); + break; + case "s": + HandleS(); + break; + case "d": + HandleD(); + break; + case "1": + setMode(Mode.Base); + console.log("base mode enabled"); + break; + case "2": + setMode(Mode.Arm); + console.log("arm mode enabled"); + break; + case "3": + setMode(Mode.Wrist); + console.log("wrist mode enabled"); + break; + default: + break; + } + + switch (event.keyCode) { + case 38: + console.log("camera moved up"); + break; + case 37: + console.log("camera moved left"); + break; + case 40: + console.log("camera moved down"); + break; + case 39: + console.log("camera moved right"); + break; + } + }, + [mode], + ); + + const HandleW = () => { + switch (mode) { + case Mode.Base: + console.log("w pressed; base mode"); + break; + case Mode.Arm: + console.log("w pressed; arm mode"); + break; + case Mode.Wrist: + console.log("w pressed; wrist mode"); + break; + default: + break; + } + }; + + const HandleA = () => { + switch (mode) { + case Mode.Base: + console.log("a pressed; base mode"); + break; + case Mode.Arm: + console.log("a pressed; arm mode"); + break; + case Mode.Wrist: + console.log("a pressed; wrist mode"); + break; + default: + break; + } + }; + + const HandleS = () => { + switch (mode) { + case Mode.Base: + console.log("s pressed; base mode"); + break; + case Mode.Arm: + console.log("s pressed; arm mode"); + break; + case Mode.Wrist: + console.log("s pressed; wrist mode"); + break; + default: + break; + } + }; + + const HandleD = () => { + switch (mode) { + case Mode.Base: + console.log("d pressed; base mode"); + break; + case Mode.Arm: + console.log("d pressed; arm mode"); + break; + case Mode.Wrist: + console.log("d pressed; wrist mode"); + break; + default: + break; + } + }; + + React.useEffect(() => { + window.addEventListener("keydown", handleKeyPress); + return () => { + window.removeEventListener("keydown", handleKeyPress); + }; + }, [handleKeyPress]); + + return ( + <> +
+ +
+ + ); +} diff --git a/src/pages/operator/tsx/index.tsx b/src/pages/operator/tsx/index.tsx index 1b4b8d09..c71c21ed 100644 --- a/src/pages/operator/tsx/index.tsx +++ b/src/pages/operator/tsx/index.tsx @@ -26,6 +26,7 @@ import { UnderVideoFunctionProvider } from "./function_providers/UnderVideoFunct import { MapFunctionProvider } from "./function_providers/MapFunctionProvider"; import { UnderMapFunctionProvider } from "./function_providers/UnderMapFunctionProvider"; import { MovementRecorderFunctionProvider } from "./function_providers/MovementRecorderFunctionProvider"; +import { KeyboardFunctionProvider } from "./function_providers/KeyboardFunctionProvider"; import { MobileOperator } from "./MobileOperator"; import { isMobile } from "react-device-detect"; import "operator/css/index.css"; @@ -57,6 +58,7 @@ export var batteryVoltageFunctionProvider = export var mapFunctionProvider: MapFunctionProvider; export var underMapFunctionProvider: UnderMapFunctionProvider; export var movementRecorderFunctionProvider: MovementRecorderFunctionProvider; +export var keyboardFunctionProvider = new KeyboardFunctionProvider(); // Create the WebRTC connection and connect the operator room connection = new WebRTCConnection({ diff --git a/src/pages/operator/tsx/layout_components/CameraView.tsx b/src/pages/operator/tsx/layout_components/CameraView.tsx index 46f6be4f..5644301a 100644 --- a/src/pages/operator/tsx/layout_components/CameraView.tsx +++ b/src/pages/operator/tsx/layout_components/CameraView.tsx @@ -237,7 +237,7 @@ export const CameraView = (props: CustomizableComponentProps) => { ) } { - // When contextMenuXY is set, display thr context menu + // When contextMenuXY is set, display the context menu contextMenuXY ? ( { + const { customizing } = props.sharedState; + const selected = isSelected(props); + + function handleSelect(event: React.MouseEvent) { + event.stopPropagation(); + props.sharedState.onSelect(props.definition, props.path); + } +}; diff --git a/src/pages/operator/tsx/utils/component_definitions.tsx b/src/pages/operator/tsx/utils/component_definitions.tsx index b3834dea..b808a1d5 100644 --- a/src/pages/operator/tsx/utils/component_definitions.tsx +++ b/src/pages/operator/tsx/utils/component_definitions.tsx @@ -25,6 +25,7 @@ export enum ComponentType { Map = "Map", RunStopButton = "Run Stop Button", BatteryGuage = "Battery Gauge", + KeyboardControl = "Keyboard Control", } /** @@ -217,3 +218,8 @@ export type MapDefinition = ComponentDefinition & { * Definition for the run stop button */ export type RunStopDefinition = ComponentDefinition; + +/** + * Definition for the keyboard + */ +export type KeyboardControlDefinition = ComponentDefinition; From 4d0b0917e92f5950bfe2c6a5c3cbf0156a393fae Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Mon, 15 Jul 2024 10:24:52 -0700 Subject: [PATCH 2/9] Configured previous commit for the stretch --- frames_2024-06-26_16.34.36.gv | 68 ++ frames_2024-06-26_16.34.36.pdf | Bin 0 -> 26377 bytes hardcopy.0 | 600 ++++++++++++++++++ .../KeyboardFunctionProvider.tsx | 8 +- src/pages/operator/tsx/index.tsx | 4 +- .../CustomizableComponent.tsx | 3 + 6 files changed, 680 insertions(+), 3 deletions(-) create mode 100644 frames_2024-06-26_16.34.36.gv create mode 100644 frames_2024-06-26_16.34.36.pdf create mode 100644 hardcopy.0 diff --git a/frames_2024-06-26_16.34.36.gv b/frames_2024-06-26_16.34.36.gv new file mode 100644 index 00000000..a9243a9a --- /dev/null +++ b/frames_2024-06-26_16.34.36.gv @@ -0,0 +1,68 @@ +digraph G { +"gripper_camera_link" -> "gripper_camera_color_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_bottom_screw_frame" -> "gripper_camera_link"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_color_frame" -> "gripper_camera_color_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_link" -> "gripper_camera_depth_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_depth_frame" -> "gripper_camera_depth_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"odom" -> "base_link"[label=" Broadcaster: default_authority\nAverage rate: 30.247\nBuffer length: 4.033\nMost recent transform: 1719444876.53824\nOldest transform: 1719444872.504752\n"]; +"base_link" -> "base_footprint"[label=" Broadcaster: default_authority\nAverage rate: 30.247\nBuffer length: 4.033\nMost recent transform: 1719444876.53824\nOldest transform: 1719444872.504752\n"]; +"link_arm_l1" -> "link_arm_l0"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_arm_l2" -> "link_arm_l1"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_arm_l3" -> "link_arm_l2"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_arm_l4" -> "link_arm_l3"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_lift" -> "link_arm_l4"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_s3_body" -> "link_gripper_finger_left"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_wrist_roll" -> "link_gripper_s3_body"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_s3_body" -> "link_gripper_finger_right"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_head" -> "link_head_pan"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_mast" -> "link_head"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_head_pan" -> "link_head_tilt"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"base_link" -> "link_left_wheel"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_mast" -> "link_lift"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"base_link" -> "link_mast"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"base_link" -> "link_right_wheel"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_wrist_yaw_bottom" -> "link_wrist_pitch"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_wrist_yaw" -> "link_wrist_yaw_bottom"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_wrist_pitch" -> "link_wrist_roll"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"link_arm_l0" -> "link_wrist_yaw"[label=" Broadcaster: default_authority\nAverage rate: 28.697\nBuffer length: 3.067\nMost recent transform: 1719444876.554189\nOldest transform: 1719444873.487645\n"]; +"camera_link" -> "camera_accel_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_bottom_screw_frame" -> "camera_link"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_accel_frame" -> "camera_accel_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_link" -> "camera_color_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_color_frame" -> "camera_color_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_link" -> "camera_depth_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_depth_frame" -> "camera_depth_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_link" -> "camera_gyro_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_gyro_frame" -> "camera_gyro_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_link" -> "camera_infra1_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_infra1_frame" -> "camera_infra1_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_link" -> "camera_infra2_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"camera_infra2_frame" -> "camera_infra2_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_head_tilt" -> "camera_bottom_screw_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"base_link" -> "caster_link"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_link" -> "gripper_camera_infra1_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_infra1_frame" -> "gripper_camera_infra1_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_link" -> "gripper_camera_infra2_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"gripper_camera_infra2_frame" -> "gripper_camera_infra2_optical_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_s3_body" -> "gripper_camera_bottom_screw_frame"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_s3_body" -> "link_aruco_d405"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_finger_left" -> "link_aruco_fingertip_left"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_finger_right" -> "link_aruco_fingertip_right"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_arm_l0" -> "link_aruco_inner_wrist"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"base_link" -> "link_aruco_left_base"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"base_link" -> "link_aruco_right_base"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_lift" -> "link_aruco_shoulder"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_arm_l0" -> "link_aruco_top_wrist"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"base_link" -> "base_imu"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_s3_body" -> "link_grasp_center"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_finger_left" -> "link_gripper_fingertip_left"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_gripper_finger_right" -> "link_gripper_fingertip_right"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_head_tilt" -> "link_head_nav_cam"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"base_link" -> "laser"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_mast" -> "respeaker_base"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +"link_wrist_roll" -> "link_wrist_quick_connect"[label=" Broadcaster: default_authority\nAverage rate: 10000.0\nBuffer length: 0.0\nMost recent transform: 0.0\nOldest transform: 0.0\n"]; +edge [style=invis]; + subgraph cluster_legend { style=bold; color=black; label ="view_frames Result"; +"Recorded at time: 1719444876.6016128"[ shape=plaintext ] ; +}->"odom"; +} diff --git a/frames_2024-06-26_16.34.36.pdf b/frames_2024-06-26_16.34.36.pdf new file mode 100644 index 0000000000000000000000000000000000000000..acef4e6449bf744a615e736e2a132ddc244634f6 GIT binary patch literal 26377 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 + // constructor(){ + // super(); + // this.provideFunctions = this.provideFunctions.bind(this); + // } + + // public provideFunctions() const handleKeyPress = React.useCallback( (event) => { switch (event.key) { diff --git a/src/pages/operator/tsx/index.tsx b/src/pages/operator/tsx/index.tsx index c71c21ed..aef369f5 100644 --- a/src/pages/operator/tsx/index.tsx +++ b/src/pages/operator/tsx/index.tsx @@ -26,7 +26,7 @@ import { UnderVideoFunctionProvider } from "./function_providers/UnderVideoFunct import { MapFunctionProvider } from "./function_providers/MapFunctionProvider"; import { UnderMapFunctionProvider } from "./function_providers/UnderMapFunctionProvider"; import { MovementRecorderFunctionProvider } from "./function_providers/MovementRecorderFunctionProvider"; -import { KeyboardFunctionProvider } from "./function_providers/KeyboardFunctionProvider"; +//import { KeyboardFunctionProvider } from "./function_providers/KeyboardFunctionProvider"; import { MobileOperator } from "./MobileOperator"; import { isMobile } from "react-device-detect"; import "operator/css/index.css"; @@ -58,7 +58,7 @@ export var batteryVoltageFunctionProvider = export var mapFunctionProvider: MapFunctionProvider; export var underMapFunctionProvider: UnderMapFunctionProvider; export var movementRecorderFunctionProvider: MovementRecorderFunctionProvider; -export var keyboardFunctionProvider = new KeyboardFunctionProvider(); +//export var keyboardFunctionProvider = new KeyboardFunctionProvider(); // Create the WebRTC connection and connect the operator room connection = new WebRTCConnection({ diff --git a/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx b/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx index f5c6b48d..502a4510 100644 --- a/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx +++ b/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx @@ -15,6 +15,7 @@ import { VirtualJoystick } from "./VirtualJoystick"; import { Map } from "./Map"; import { RunStopButton } from "../static_components/RunStop"; import { BatteryGuage } from "../static_components/BatteryGauge"; +import KeyboardFunctionProvider from "../function_providers/KeyboardFunctionProvider"; /** State required for all elements */ export type SharedState = { @@ -86,6 +87,8 @@ export const CustomizableComponent = (props: CustomizableComponentProps) => { return ; case ComponentType.BatteryGuage: return ; + case ComponentType.KeyboardControl: + return ; default: throw Error( `CustomizableComponent cannot render component of unknown type: ${props.definition.type}\nYou may need to add a case for this component in the switch statement in CustomizableComponent.`, From ba25b2e2203f5a1ca7e4977d46a313dd58ff4e00 Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Tue, 16 Jul 2024 15:16:53 -0700 Subject: [PATCH 3/9] Keyboard inputs now produce robot movement. Need to adjust logic for keys in diff action modes --- .../KeyboardFunctionProvider.tsx | 367 ++++++++++++------ src/pages/operator/tsx/index.tsx | 4 +- .../CustomizableComponent.tsx | 4 +- .../tsx/layout_components/KeyboardControl.tsx | 42 ++ 4 files changed, 290 insertions(+), 127 deletions(-) diff --git a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx index 5437b5d5..4dec06b8 100644 --- a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx +++ b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx @@ -1,145 +1,266 @@ import React from "react"; +import { buttonFunctionProvider } from "operator/tsx/index"; +import { FunctionProvider } from "./FunctionProvider"; +import { ButtonPadButton, ButtonFunctions } from "./ButtonFunctionProvider"; +import { ActionMode } from "../utils/component_definitions"; -enum Mode { +export enum Mode { Base = "base", Arm = "arm", Wrist = "wrist", } -export default function KeyboardFunctionProvider() { - const [mode, setMode] = React.useState(); +export class KeyboardFunctionProvider extends FunctionProvider { + constructor() { + super(); + this.provideFunctions = this.provideFunctions.bind(this); + } - // constructor(){ - // super(); - // this.provideFunctions = this.provideFunctions.bind(this); - // } - - // public provideFunctions() - const handleKeyPress = React.useCallback( - (event) => { - switch (event.key) { - case "w": - HandleW(); - break; - case "a": - HandleA(); - break; - case "s": - HandleS(); - break; - case "d": - HandleD(); - break; - case "1": - setMode(Mode.Base); - console.log("base mode enabled"); - break; - case "2": - setMode(Mode.Arm); - console.log("arm mode enabled"); - break; - case "3": - setMode(Mode.Wrist); - console.log("wrist mode enabled"); - break; - default: - break; - } - - switch (event.keyCode) { - case 38: - console.log("camera moved up"); - break; - case 37: - console.log("camera moved left"); - break; - case 40: - console.log("camera moved down"); - break; - case 39: - console.log("camera moved right"); - break; - } - }, - [mode], - ); - - const HandleW = () => { - switch (mode) { - case Mode.Base: - console.log("w pressed; base mode"); - break; - case Mode.Arm: - console.log("w pressed; arm mode"); - break; - case Mode.Wrist: - console.log("w pressed; wrist mode"); - break; - default: + private getButtonPadButton( + KeyboardFunction: Mode, + key: string, + ): ButtonPadButton { + // TODO + let keyInput: ButtonPadButton; + switch (key) { + case "w": + switch (KeyboardFunction) { + case Mode.Base: + console.log("w pressed; base mode"); + keyInput = ButtonPadButton.BaseForward; + break; + case Mode.Arm: + console.log("w pressed; arm mode"); + keyInput = ButtonPadButton.ArmLift; + break; + case Mode.Wrist: + console.log("w pressed; wrist mode"); + keyInput = ButtonPadButton.WristPitchUp; + break; + default: + break; + } break; - } - }; - - const HandleA = () => { - switch (mode) { - case Mode.Base: - console.log("a pressed; base mode"); + case "a": + switch (KeyboardFunction) { + case Mode.Base: + console.log("a pressed; base mode"); + keyInput = ButtonPadButton.BaseRotateLeft; + break; + case Mode.Arm: + console.log("a pressed; arm mode"); + keyInput = ButtonPadButton.ArmRetract; + break; + case Mode.Wrist: + console.log("a pressed; wrist mode"); + keyInput = ButtonPadButton.WristRollLeft; + break; + default: + break; + } break; - case Mode.Arm: - console.log("a pressed; arm mode"); + case "s": + switch (KeyboardFunction) { + case Mode.Base: + console.log("s pressed; base mode"); + keyInput = ButtonPadButton.BaseReverse; + break; + case Mode.Arm: + console.log("s pressed; arm mode"); + keyInput = ButtonPadButton.ArmLower; + break; + case Mode.Wrist: + console.log("s pressed; wrist mode"); + keyInput = ButtonPadButton.WristPitchDown; + break; + default: + break; + } break; - case Mode.Wrist: - console.log("a pressed; wrist mode"); + case "d": + switch (KeyboardFunction) { + case Mode.Base: + console.log("d pressed; base mode"); + keyInput = ButtonPadButton.BaseRotateRight; + break; + case Mode.Arm: + console.log("d pressed; arm mode"); + keyInput = ButtonPadButton.ArmExtend; + break; + case Mode.Wrist: + console.log("d pressed; wrist mode"); + keyInput = ButtonPadButton.WristRollRight; + break; + default: + break; + } break; - default: + case "ArrowUp": + console.log("camera moved up"); + keyInput = ButtonPadButton.CameraTiltUp; break; - } - }; - - const HandleS = () => { - switch (mode) { - case Mode.Base: - console.log("s pressed; base mode"); + case "ArrowLeft": + console.log("camera moved left"); + keyInput = ButtonPadButton.CameraPanLeft; break; - case Mode.Arm: - console.log("s pressed; arm mode"); + case "ArrowDown": + console.log("camera moved down"); + keyInput = ButtonPadButton.CameraTiltDown; break; - case Mode.Wrist: - console.log("s pressed; wrist mode"); + case "ArrowRight": + console.log("camera moved right"); + keyInput = ButtonPadButton.CameraPanRight; break; default: + console.log("Unmapped key", key); break; } - }; + return keyInput; + } - const HandleD = () => { - switch (mode) { - case Mode.Base: - console.log("d pressed; base mode"); - break; - case Mode.Arm: - console.log("d pressed; arm mode"); - break; - case Mode.Wrist: - console.log("d pressed; wrist mode"); - break; - default: - break; - } - }; + public provideFunctions( + keyboardFunction: Mode, + key: string, + ): ButtonFunctions { + let button = this.getButtonPadButton(keyboardFunction, key); + return buttonFunctionProvider.provideFunctions(button); + } +} - React.useEffect(() => { - window.addEventListener("keydown", handleKeyPress); - return () => { - window.removeEventListener("keydown", handleKeyPress); - }; - }, [handleKeyPress]); +// export default function KeyboardFunctionProvider() { +// const [mode, setMode] = React.useState(); - return ( - <> -
- -
- - ); -} +// // constructor(){ +// // super(); +// // this.provideFunctions = this.provideFunctions.bind(this); +// // } + +// // public provideFunctions() +// const handleKeyPress = React.useCallback( +// (event) => { +// switch (event.key) { +// case "w": +// HandleW(); +// break; +// case "a": +// HandleA(); +// break; +// case "s": +// HandleS(); +// break; +// case "d": +// HandleD(); +// break; +// case "1": +// setMode(Mode.Base); +// console.log("base mode enabled"); +// break; +// case "2": +// setMode(Mode.Arm); +// console.log("arm mode enabled"); +// break; +// case "3": +// setMode(Mode.Wrist); +// console.log("wrist mode enabled"); +// break; +// default: +// break; +// } + +// switch (event.keyCode) { +// case 38: +// console.log("camera moved up"); +// break; +// case 37: +// console.log("camera moved left"); +// break; +// case 40: +// console.log("camera moved down"); +// break; +// case 39: +// console.log("camera moved right"); +// break; +// } +// }, +// [mode], +// ); + +// const HandleW = () => { +// switch (mode) { +// case Mode.Base: +// console.log("w pressed; base mode"); +// break; +// case Mode.Arm: +// console.log("w pressed; arm mode"); +// break; +// case Mode.Wrist: +// console.log("w pressed; wrist mode"); +// break; +// default: +// break; +// } +// }; + +// const HandleA = () => { +// switch (mode) { +// case Mode.Base: +// console.log("a pressed; base mode"); +// break; +// case Mode.Arm: +// console.log("a pressed; arm mode"); +// break; +// case Mode.Wrist: +// console.log("a pressed; wrist mode"); +// break; +// default: +// break; +// } +// }; + +// const HandleS = () => { +// switch (mode) { +// case Mode.Base: +// console.log("s pressed; base mode"); +// break; +// case Mode.Arm: +// console.log("s pressed; arm mode"); +// break; +// case Mode.Wrist: +// console.log("s pressed; wrist mode"); +// break; +// default: +// break; +// } +// }; + +// const HandleD = () => { +// switch (mode) { +// case Mode.Base: +// console.log("d pressed; base mode"); +// break; +// case Mode.Arm: +// console.log("d pressed; arm mode"); +// break; +// case Mode.Wrist: +// console.log("d pressed; wrist mode"); +// break; +// default: +// break; +// } +// }; + +// React.useEffect(() => { +// window.addEventListener("keydown", handleKeyPress); +// return () => { +// window.removeEventListener("keydown", handleKeyPress); +// }; +// }, [handleKeyPress]); + +// return ( +// <> +//
+// +//
+// +// ); +// } diff --git a/src/pages/operator/tsx/index.tsx b/src/pages/operator/tsx/index.tsx index aef369f5..c71c21ed 100644 --- a/src/pages/operator/tsx/index.tsx +++ b/src/pages/operator/tsx/index.tsx @@ -26,7 +26,7 @@ import { UnderVideoFunctionProvider } from "./function_providers/UnderVideoFunct import { MapFunctionProvider } from "./function_providers/MapFunctionProvider"; import { UnderMapFunctionProvider } from "./function_providers/UnderMapFunctionProvider"; import { MovementRecorderFunctionProvider } from "./function_providers/MovementRecorderFunctionProvider"; -//import { KeyboardFunctionProvider } from "./function_providers/KeyboardFunctionProvider"; +import { KeyboardFunctionProvider } from "./function_providers/KeyboardFunctionProvider"; import { MobileOperator } from "./MobileOperator"; import { isMobile } from "react-device-detect"; import "operator/css/index.css"; @@ -58,7 +58,7 @@ export var batteryVoltageFunctionProvider = export var mapFunctionProvider: MapFunctionProvider; export var underMapFunctionProvider: UnderMapFunctionProvider; export var movementRecorderFunctionProvider: MovementRecorderFunctionProvider; -//export var keyboardFunctionProvider = new KeyboardFunctionProvider(); +export var keyboardFunctionProvider = new KeyboardFunctionProvider(); // Create the WebRTC connection and connect the operator room connection = new WebRTCConnection({ diff --git a/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx b/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx index 502a4510..11a166b1 100644 --- a/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx +++ b/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx @@ -15,7 +15,7 @@ import { VirtualJoystick } from "./VirtualJoystick"; import { Map } from "./Map"; import { RunStopButton } from "../static_components/RunStop"; import { BatteryGuage } from "../static_components/BatteryGauge"; -import KeyboardFunctionProvider from "../function_providers/KeyboardFunctionProvider"; +import { KeyboardControl } from "./KeyboardControl"; /** State required for all elements */ export type SharedState = { @@ -88,7 +88,7 @@ export const CustomizableComponent = (props: CustomizableComponentProps) => { case ComponentType.BatteryGuage: return ; case ComponentType.KeyboardControl: - return ; + return ; default: throw Error( `CustomizableComponent cannot render component of unknown type: ${props.definition.type}\nYou may need to add a case for this component in the switch statement in CustomizableComponent.`, diff --git a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx index 82c31c37..6a90426e 100644 --- a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx +++ b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx @@ -5,13 +5,55 @@ import { isSelected, } from "./CustomizableComponent"; import { keyboardFunctionProvider } from "operator/tsx/index"; +import { Mode } from "../function_providers/KeyboardFunctionProvider"; export const KeyboardControl = (props: CustomizableComponentProps) => { + const [mode, setMode] = React.useState(Mode.Base); + const { customizing } = props.sharedState; const selected = isSelected(props); + const functions = []; function handleSelect(event: React.MouseEvent) { event.stopPropagation(); props.sharedState.onSelect(props.definition, props.path); } + + const handleKeyPress = React.useCallback( + (event) => { + switch (event.key) { + case "1": + setMode(Mode.Base); + console.log("base mode enabled"); + break; + case "2": + setMode(Mode.Arm); + console.log("arm mode enabled"); + break; + case "3": + setMode(Mode.Wrist); + console.log("wrist mode enabled"); + break; + } + + let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); + functs.onClick(); + }, + [mode], + ); + + React.useEffect(() => { + window.addEventListener("keydown", handleKeyPress); + return () => { + window.removeEventListener("keydown", handleKeyPress); + }; + }, [handleKeyPress]); + + return ( + <> +
+ +
+ + ); }; From 083ad83fde3d46ab3f9a97144c595b271700d2ef Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Wed, 17 Jul 2024 17:15:04 -0700 Subject: [PATCH 4/9] keyboard inputs integrated; minor bugs fixed --- .../KeyboardFunctionProvider.tsx | 171 ++++-------------- .../tsx/layout_components/KeyboardControl.tsx | 36 +++- 2 files changed, 62 insertions(+), 145 deletions(-) diff --git a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx index 4dec06b8..6dab3860 100644 --- a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx +++ b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx @@ -53,7 +53,7 @@ export class KeyboardFunctionProvider extends FunctionProvider { break; case Mode.Wrist: console.log("a pressed; wrist mode"); - keyInput = ButtonPadButton.WristRollLeft; + keyInput = ButtonPadButton.WristRotateIn; break; default: break; @@ -89,12 +89,40 @@ export class KeyboardFunctionProvider extends FunctionProvider { break; case Mode.Wrist: console.log("d pressed; wrist mode"); + keyInput = ButtonPadButton.WristRotateOut; + break; + default: + break; + } + break; + case "q": + switch (KeyboardFunction) { + case Mode.Wrist: + console.log("q pressed; wrist mode"); + keyInput = ButtonPadButton.WristRollLeft; + break; + default: + break; + } + break; + case "e": + switch (KeyboardFunction) { + case Mode.Wrist: + console.log("e pressed; wrist mode"); keyInput = ButtonPadButton.WristRollRight; break; default: break; } break; + case "j": + console.log("j pressed; gripper closed"); + keyInput = ButtonPadButton.GripperClose; + break; + case "k": + console.log("k pressed; gripper opened"); + keyInput = ButtonPadButton.GripperOpen; + break; case "ArrowUp": console.log("camera moved up"); keyInput = ButtonPadButton.CameraTiltUp; @@ -111,6 +139,8 @@ export class KeyboardFunctionProvider extends FunctionProvider { console.log("camera moved right"); keyInput = ButtonPadButton.CameraPanRight; break; + case "": + console.log("Key Released"); default: console.log("Unmapped key", key); break; @@ -122,145 +152,8 @@ export class KeyboardFunctionProvider extends FunctionProvider { keyboardFunction: Mode, key: string, ): ButtonFunctions { + // switch() let button = this.getButtonPadButton(keyboardFunction, key); return buttonFunctionProvider.provideFunctions(button); } } - -// export default function KeyboardFunctionProvider() { -// const [mode, setMode] = React.useState(); - -// // constructor(){ -// // super(); -// // this.provideFunctions = this.provideFunctions.bind(this); -// // } - -// // public provideFunctions() -// const handleKeyPress = React.useCallback( -// (event) => { -// switch (event.key) { -// case "w": -// HandleW(); -// break; -// case "a": -// HandleA(); -// break; -// case "s": -// HandleS(); -// break; -// case "d": -// HandleD(); -// break; -// case "1": -// setMode(Mode.Base); -// console.log("base mode enabled"); -// break; -// case "2": -// setMode(Mode.Arm); -// console.log("arm mode enabled"); -// break; -// case "3": -// setMode(Mode.Wrist); -// console.log("wrist mode enabled"); -// break; -// default: -// break; -// } - -// switch (event.keyCode) { -// case 38: -// console.log("camera moved up"); -// break; -// case 37: -// console.log("camera moved left"); -// break; -// case 40: -// console.log("camera moved down"); -// break; -// case 39: -// console.log("camera moved right"); -// break; -// } -// }, -// [mode], -// ); - -// const HandleW = () => { -// switch (mode) { -// case Mode.Base: -// console.log("w pressed; base mode"); -// break; -// case Mode.Arm: -// console.log("w pressed; arm mode"); -// break; -// case Mode.Wrist: -// console.log("w pressed; wrist mode"); -// break; -// default: -// break; -// } -// }; - -// const HandleA = () => { -// switch (mode) { -// case Mode.Base: -// console.log("a pressed; base mode"); -// break; -// case Mode.Arm: -// console.log("a pressed; arm mode"); -// break; -// case Mode.Wrist: -// console.log("a pressed; wrist mode"); -// break; -// default: -// break; -// } -// }; - -// const HandleS = () => { -// switch (mode) { -// case Mode.Base: -// console.log("s pressed; base mode"); -// break; -// case Mode.Arm: -// console.log("s pressed; arm mode"); -// break; -// case Mode.Wrist: -// console.log("s pressed; wrist mode"); -// break; -// default: -// break; -// } -// }; - -// const HandleD = () => { -// switch (mode) { -// case Mode.Base: -// console.log("d pressed; base mode"); -// break; -// case Mode.Arm: -// console.log("d pressed; arm mode"); -// break; -// case Mode.Wrist: -// console.log("d pressed; wrist mode"); -// break; -// default: -// break; -// } -// }; - -// React.useEffect(() => { -// window.addEventListener("keydown", handleKeyPress); -// return () => { -// window.removeEventListener("keydown", handleKeyPress); -// }; -// }, [handleKeyPress]); - -// return ( -// <> -//
-// -//
-// -// ); -// } diff --git a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx index 6a90426e..50ab450e 100644 --- a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx +++ b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx @@ -9,6 +9,7 @@ import { Mode } from "../function_providers/KeyboardFunctionProvider"; export const KeyboardControl = (props: CustomizableComponentProps) => { const [mode, setMode] = React.useState(Mode.Base); + const [keyPressed, setKeyPressed] = React.useState(false); const { customizing } = props.sharedState; const selected = isSelected(props); @@ -21,33 +22,56 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { const handleKeyPress = React.useCallback( (event) => { + if (keyPressed === true) { + return; + } + console.log(keyPressed); + + setKeyPressed(true); + switch (event.key) { case "1": setMode(Mode.Base); console.log("base mode enabled"); break; case "2": - setMode(Mode.Arm); - console.log("arm mode enabled"); - break; - case "3": setMode(Mode.Wrist); console.log("wrist mode enabled"); break; + case "3": + setMode(Mode.Arm); + console.log("arm mode enabled"); + break; } let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); functs.onClick(); }, - [mode], + [mode, keyPressed], + ); + + const handleKeyRelease = React.useCallback( + (event) => { + console.log("Key Released"); + setKeyPressed(false); + let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); + functs.onRelease(); + }, + [mode, keyPressed], ); React.useEffect(() => { + //window.onkeydown = handleKeyPress; + // window.onkeyup = function(){ + // this.onkeydown = handleKeyPress; + // } window.addEventListener("keydown", handleKeyPress); + window.addEventListener("keyup", handleKeyRelease); return () => { window.removeEventListener("keydown", handleKeyPress); + window.removeEventListener("keyup", handleKeyRelease); }; - }, [handleKeyPress]); + }, [handleKeyPress, handleKeyRelease]); return ( <> From d067dd85ed9d4322ec371d2003bd8eefce787a54 Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Wed, 24 Jul 2024 16:53:29 -0700 Subject: [PATCH 5/9] Improving keyboard component UI --- src/pages/operator/css/KeyboardControl.css | 121 +++++++++ .../tsx/default_layouts/SIMPLE_LAYOUT.tsx | 4 - .../KeyboardFunctionProvider.tsx | 22 +- .../tsx/layout_components/KeyboardControl.tsx | 239 +++++++++++++++++- .../tsx/static_components/Sidebar.tsx | 2 + 5 files changed, 372 insertions(+), 16 deletions(-) create mode 100644 src/pages/operator/css/KeyboardControl.css diff --git a/src/pages/operator/css/KeyboardControl.css b/src/pages/operator/css/KeyboardControl.css new file mode 100644 index 00000000..375c74e0 --- /dev/null +++ b/src/pages/operator/css/KeyboardControl.css @@ -0,0 +1,121 @@ +.keyboard-control { + /* display: grid; + flex: 2; + justify-content: center; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(3, 1fr); + border-radius: var(--radius); */ + + background-color: #f4f7f8; + margin: 1em; + display: grid; + resize: horizontal; +} + +.keyboard-row { + display: flex; +} + +.keyboard-column { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: auto; +} + +.item { + /* margin: 0.5em; + padding: 0.5em; + min-width: 0; */ + /* background-color: #1b5385; + color: white; + font-family: monospace; + font-size: 13px; */ + flex: auto; +} + +/* WASD AND ARROW KEYS LAYOUT */ +.controls-container { + position: relative; + display: grid; + margin: 0.5em; + grid-template-rows: repeat(3, 1fr); + align-items: center; + justify-content: center; + text-align: center; + flex: auto; +} + +.controls-column { + position: relative; + justify-self: center; + align-items: center; +} + +.gripper-container { + position: relative; + display: grid; + grid-template-rows: repeat(2, 1fr); + align-items: center; + justify-content: center; + flex: auto; + margin: 0.5em; +} +.gripper-column { + position: relative; + justify-self: center; + align-items: center; +} + +.keyboard-button.active { + fill-opacity: 60%; + background-color: hsl(200, 50%, 60%); + color: hsl(200, 0%, 60%); +} + +/* BUTTON MODES */ +.mode-button { + stroke-linecap: round; + stroke-linejoin: round; + position: relative; + grid-template-rows: repeat(2, 1fr); + align-items: center; + justify-content: center; +} + +.mode-button.active { + background-color: #1b5385; +} + +.current-mode { + /* margin-bottom: 0.5em; */ +} + +/* TOGGLE BUTTON */ +.toggle-key { + display: block; + margin-right: auto; + margin-left: auto; + font-size: 1em; +} + +.toggle-key.active { + background-color: green; + color: white; +} + +.toggle-key.off { + background-color: red; +} + +/* TODO: MAKE LAYOUT REACTIVE TO SCREEN SIZE */ +@media screen and (orientation: portrait) { + .keyboard-control { + width: 100%; + } +} + +button { + background-color: hsl(0, 0%, 31%); +} diff --git a/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx b/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx index 4109d728..133ef8ef 100644 --- a/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx +++ b/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx @@ -44,10 +44,6 @@ export const BASIC_LAYOUT: LayoutDefinition = { displayButtons: true, children: [], } as CameraViewDefinition, - { - //KEYBOARD CONTROL - type: ComponentType.KeyboardControl, - } as KeyboardControlDefinition, ], }, ], diff --git a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx index 6dab3860..e8203cf5 100644 --- a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx +++ b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx @@ -3,24 +3,38 @@ import { buttonFunctionProvider } from "operator/tsx/index"; import { FunctionProvider } from "./FunctionProvider"; import { ButtonPadButton, ButtonFunctions } from "./ButtonFunctionProvider"; import { ActionMode } from "../utils/component_definitions"; +// import { keyState } from "../layout_components/KeyboardControl" export enum Mode { - Base = "base", - Arm = "arm", - Wrist = "wrist", + Base = "Base", + Arm = "Arm", + Wrist = "Wrist", } +// const [keyState, setKeyState] = React.useState({ +// W: false, +// A: false, +// S: false, +// D: false, +// J: false, +// K: false, +// UpArrow: false, +// LeftArrow: false, +// DownArrow: false, +// RightArrow: false, +// }); + export class KeyboardFunctionProvider extends FunctionProvider { constructor() { super(); this.provideFunctions = this.provideFunctions.bind(this); + /// buttonFunctionProvider.updateJointStates = buttonFunctionProvider.updateJointStates.bind(this); } private getButtonPadButton( KeyboardFunction: Mode, key: string, ): ButtonPadButton { - // TODO let keyInput: ButtonPadButton; switch (key) { case "w": diff --git a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx index 50ab450e..49b83d1b 100644 --- a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx +++ b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx @@ -6,14 +6,33 @@ import { } from "./CustomizableComponent"; import { keyboardFunctionProvider } from "operator/tsx/index"; import { Mode } from "../function_providers/KeyboardFunctionProvider"; +import "operator/css/ButtonGrid.css"; +import "operator/css/KeyboardControl.css"; +import { className } from "shared/util"; export const KeyboardControl = (props: CustomizableComponentProps) => { const [mode, setMode] = React.useState(Mode.Base); - const [keyPressed, setKeyPressed] = React.useState(false); + const [keyPressed, setKeyPressed] = React.useState(false); + const [activeMode, setActiveMode] = React.useState(1); + const [toggleState, setToggleState] = React.useState(true); const { customizing } = props.sharedState; const selected = isSelected(props); - const functions = []; + + const [keyState, setKeyState] = React.useState({ + w: false, + a: false, + s: false, + d: false, + q: false, + e: false, + j: false, + k: false, + ArrowUp: false, + ArrowLeft: false, + ArrowDown: false, + ArrowRight: false, + }); function handleSelect(event: React.MouseEvent) { event.stopPropagation(); @@ -25,27 +44,30 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { if (keyPressed === true) { return; } - console.log(keyPressed); - setKeyPressed(true); switch (event.key) { case "1": setMode(Mode.Base); + setActiveMode(1); console.log("base mode enabled"); break; case "2": setMode(Mode.Wrist); + setActiveMode(2); console.log("wrist mode enabled"); break; case "3": setMode(Mode.Arm); + setActiveMode(3); console.log("arm mode enabled"); break; } let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); functs.onClick(); + setKeyState((prevState) => ({ ...prevState, [event.key]: true })); + //functs.active(); }, [mode, keyPressed], ); @@ -54,6 +76,8 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { (event) => { console.log("Key Released"); setKeyPressed(false); + setKeyState((prevState) => ({ ...prevState, [event.key]: false })); + let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); functs.onRelease(); }, @@ -73,11 +97,210 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { }; }, [handleKeyPress, handleKeyRelease]); + const ToggleButton = () => { + setToggleState((prevToggleState) => !prevToggleState); + }; + return ( - <> -
- +
+
+
+
Current Mode: {mode}
+
+ + + +
+
+
+
Toggle Keys?
+ +
+
+
+
+ Camera Controls
+
+ +
+
+ + + +
+
+
+ Gripper Controls
+
+ + +
+
+
+
+
+ Mode Controls +
+
+ {activeMode === 2 && ( + + )} + + {activeMode === 2 && ( + + )} +
+
+ + + +
+
- +
+ //
+ //
+ // Mode Buttons
+ // + // + // + //
+ + //
Current Mode: {mode}
+ + //
+ // Toggle Controls
+ // + //
+ + //
+ // Mode Controls
+ //
+ // {activeMode === 2 && ()} + // + // {/* className = {keyState.W === true ? "active" : undefined } */} + // {activeMode === 2 && ()} + //
+ //
+ // + // + // + //
+ //
+ + //
+ // Gripper Controls
+ // + // + //
+ + //
+ // Camera Controls
+ //
+ // + //
+ //
+ // + // + // + //
+ //
+ //
); }; diff --git a/src/pages/operator/tsx/static_components/Sidebar.tsx b/src/pages/operator/tsx/static_components/Sidebar.tsx index 02b6a035..e5cd6157 100644 --- a/src/pages/operator/tsx/static_components/Sidebar.tsx +++ b/src/pages/operator/tsx/static_components/Sidebar.tsx @@ -90,6 +90,7 @@ function componentDescription(definition: ComponentDefinition): string { case ComponentType.VirtualJoystick: case ComponentType.ButtonGrid: case ComponentType.Map: + case ComponentType.KeyboardControl: return definition.type; default: throw Error( @@ -466,6 +467,7 @@ const SidebarComponentProvider = (props: SidebarComponentProviderProps) => { { type: ComponentType.ButtonGrid }, { type: ComponentType.VirtualJoystick }, { type: ComponentType.Map }, + { type: ComponentType.KeyboardControl }, ]; function handleSelect(type: ComponentType, id?: ComponentId) { From 3d7d9dcbbedd15fa7b7a134449f15346efe9e43b Mon Sep 17 00:00:00 2001 From: Amal Nanavati Date: Thu, 25 Jul 2024 17:18:20 -0700 Subject: [PATCH 6/9] small changes to UI; toggle button now works --- src/pages/operator/css/KeyboardControl.css | 38 ++- .../tsx/layout_components/KeyboardControl.tsx | 238 ++++++++---------- 2 files changed, 131 insertions(+), 145 deletions(-) diff --git a/src/pages/operator/css/KeyboardControl.css b/src/pages/operator/css/KeyboardControl.css index 375c74e0..79d216ea 100644 --- a/src/pages/operator/css/KeyboardControl.css +++ b/src/pages/operator/css/KeyboardControl.css @@ -12,6 +12,20 @@ resize: horizontal; } +.keyboard-control.selected { + opacity: 100%; + stroke: var(--selected-color); + opacity: 100%; + border: 1px solid var(--selected-color); + stroke-width: 5px; + box-shadow: 0 0 1rem 0.5rem var(--selected-color); + pointer-events: none; +} + +.keyboard-control.customizing path { + pointer-events: none; +} + .keyboard-row { display: flex; } @@ -21,7 +35,8 @@ flex-direction: column; justify-content: center; align-items: center; - flex: auto; + margin: 0.5em; + flex: 1; } .item { @@ -41,7 +56,7 @@ display: grid; margin: 0.5em; grid-template-rows: repeat(3, 1fr); - align-items: center; + align-items: flex-start; justify-content: center; text-align: center; flex: auto; @@ -57,7 +72,7 @@ position: relative; display: grid; grid-template-rows: repeat(2, 1fr); - align-items: center; + align-items: flex-start; justify-content: center; flex: auto; margin: 0.5em; @@ -71,7 +86,7 @@ .keyboard-button.active { fill-opacity: 60%; background-color: hsl(200, 50%, 60%); - color: hsl(200, 0%, 60%); + color: hsl(0, 0%, 79%); } /* BUTTON MODES */ @@ -79,13 +94,13 @@ stroke-linecap: round; stroke-linejoin: round; position: relative; - grid-template-rows: repeat(2, 1fr); - align-items: center; - justify-content: center; + margin-top: 0.1em; + margin-inline: 0.1em; } .mode-button.active { - background-color: #1b5385; + background-color: hsl(200, 50%, 60%); + color: hsl(0, 0%, 79%); } .current-mode { @@ -95,18 +110,17 @@ /* TOGGLE BUTTON */ .toggle-key { display: block; - margin-right: auto; - margin-left: auto; font-size: 1em; } .toggle-key.active { - background-color: green; + background-color: var(--btn-turquoise); color: white; } .toggle-key.off { - background-color: red; + background-color: var(--btn-red); + color: black; } /* TODO: MAKE LAYOUT REACTIVE TO SCREEN SIZE */ diff --git a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx index 49b83d1b..5bb68c6e 100644 --- a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx +++ b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx @@ -9,6 +9,12 @@ import { Mode } from "../function_providers/KeyboardFunctionProvider"; import "operator/css/ButtonGrid.css"; import "operator/css/KeyboardControl.css"; import { className } from "shared/util"; +import { + ButtonPadShape, + getIcon, + getPathsFromShape, + SVG_RESOLUTION, +} from "../utils/svg"; export const KeyboardControl = (props: CustomizableComponentProps) => { const [mode, setMode] = React.useState(Mode.Base); @@ -19,6 +25,17 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { const { customizing } = props.sharedState; const selected = isSelected(props); + function handleSelect(event: React.MouseEvent) { + event.stopPropagation(); + props.sharedState.onSelect(props.definition, props.path); + } + + const selectProp = customizing + ? { + onClick: handleSelect, + } + : {}; + const [keyState, setKeyState] = React.useState({ w: false, a: false, @@ -34,10 +51,10 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { ArrowRight: false, }); - function handleSelect(event: React.MouseEvent) { - event.stopPropagation(); - props.sharedState.onSelect(props.definition, props.path); - } + const toggleButton = () => { + setToggleState((prevToggleState) => !prevToggleState); + console.log("keys on?", toggleState); + }; const handleKeyPress = React.useCallback( (event) => { @@ -67,14 +84,12 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); functs.onClick(); setKeyState((prevState) => ({ ...prevState, [event.key]: true })); - //functs.active(); }, [mode, keyPressed], ); const handleKeyRelease = React.useCallback( (event) => { - console.log("Key Released"); setKeyPressed(false); setKeyState((prevState) => ({ ...prevState, [event.key]: false })); @@ -85,44 +100,60 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { ); React.useEffect(() => { - //window.onkeydown = handleKeyPress; - // window.onkeyup = function(){ - // this.onkeydown = handleKeyPress; - // } - window.addEventListener("keydown", handleKeyPress); - window.addEventListener("keyup", handleKeyRelease); + if (toggleState) { + window.addEventListener("keydown", handleKeyPress); + window.addEventListener("keyup", handleKeyRelease); + } else { + window.removeEventListener("keydown", handleKeyPress); + window.removeEventListener("keyup", handleKeyRelease); + { + handleKeyRelease; + } + } return () => { window.removeEventListener("keydown", handleKeyPress); window.removeEventListener("keyup", handleKeyRelease); }; - }, [handleKeyPress, handleKeyRelease]); - - const ToggleButton = () => { - setToggleState((prevToggleState) => !prevToggleState); - }; + }, [toggleState, handleKeyPress, handleKeyRelease]); return ( -
+
-
Current Mode: {mode}
+
-
-
Toggle Keys?
- -
- Camera Controls
+ {mode} Mode Controls +
+ {activeMode === 2 && ( + + )} + {activeMode === 2 && ( + + )}
-
- Gripper Controls
-
+
+
+
+ Camera Controls
+
+
+
-
-
-
-
-
- Mode Controls -
-
- {activeMode === 2 && ( - - )} - {activeMode === 2 && ( - - )} -
-
+
+
+
+ Gripper Controls
+
- //
- //
- // Mode Buttons
- // - // - // - //
- - //
Current Mode: {mode}
- - //
- // Toggle Controls
- // - //
- - //
- // Mode Controls
- //
- // {activeMode === 2 && ()} - // - // {/* className = {keyState.W === true ? "active" : undefined } */} - // {activeMode === 2 && ()} - //
- //
- // - // - // - //
- //
- - //
- // Gripper Controls
- // - // - //
- - //
- // Camera Controls
- //
- // - //
- //
- // - // - // - //
- //
- //
); }; From c3e9ede78b2a90e5a874bc4bab7584f40566b252 Mon Sep 17 00:00:00 2001 From: Neranti Gary Date: Fri, 26 Jul 2024 17:11:00 -0700 Subject: [PATCH 7/9] fixed some bugs regarding key inputs while in customize mode --- src/pages/operator/css/KeyboardControl.css | 24 +------ .../tsx/layout_components/KeyboardControl.tsx | 63 +++++++++++++------ 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/pages/operator/css/KeyboardControl.css b/src/pages/operator/css/KeyboardControl.css index 79d216ea..012eaf25 100644 --- a/src/pages/operator/css/KeyboardControl.css +++ b/src/pages/operator/css/KeyboardControl.css @@ -1,15 +1,8 @@ .keyboard-control { - /* display: grid; - flex: 2; - justify-content: center; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(3, 1fr); - border-radius: var(--radius); */ - background-color: #f4f7f8; margin: 1em; display: grid; - resize: horizontal; + resize: vertical; } .keyboard-control.selected { @@ -39,17 +32,6 @@ flex: 1; } -.item { - /* margin: 0.5em; - padding: 0.5em; - min-width: 0; */ - /* background-color: #1b5385; - color: white; - font-family: monospace; - font-size: 13px; */ - flex: auto; -} - /* WASD AND ARROW KEYS LAYOUT */ .controls-container { position: relative; @@ -103,10 +85,6 @@ color: hsl(0, 0%, 79%); } -.current-mode { - /* margin-bottom: 0.5em; */ -} - /* TOGGLE BUTTON */ .toggle-key { display: block; diff --git a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx index 5bb68c6e..37ff34c8 100644 --- a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx +++ b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx @@ -100,27 +100,24 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { ); React.useEffect(() => { - if (toggleState) { + if (toggleState && !customizing) { window.addEventListener("keydown", handleKeyPress); window.addEventListener("keyup", handleKeyRelease); } else { + setKeyPressed(false); window.removeEventListener("keydown", handleKeyPress); window.removeEventListener("keyup", handleKeyRelease); - { - handleKeyRelease; - } } return () => { window.removeEventListener("keydown", handleKeyPress); window.removeEventListener("keyup", handleKeyRelease); }; - }, [toggleState, handleKeyPress, handleKeyRelease]); + }, [toggleState, handleKeyPress, handleKeyRelease, customizing]); return (
@@ -169,7 +166,9 @@ export const KeyboardControl = (props: CustomizableComponentProps) => {
{activeMode === 2 && ( )} {activeMode === 2 && (
+
+ + +
)} - {activeMode === 2 && ( + {activeMode === 3 && ( )}
@@ -232,9 +335,7 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { Camera Controls
From 9d89b2f9aa08d91fbb7319200e278d507602ad5e Mon Sep 17 00:00:00 2001 From: Neranti Gary Date: Fri, 2 Aug 2024 15:17:42 -0700 Subject: [PATCH 9/9] Last updates to UI functionality --- src/pages/operator/css/KeyboardControl.css | 83 +++++++----- src/pages/operator/tsx/Operator.tsx | 2 +- .../ButtonFunctionProvider.tsx | 40 +++--- .../KeyboardFunctionProvider.tsx | 14 +-- .../tsx/layout_components/KeyboardControl.tsx | 119 +++++++----------- 5 files changed, 124 insertions(+), 134 deletions(-) diff --git a/src/pages/operator/css/KeyboardControl.css b/src/pages/operator/css/KeyboardControl.css index 1c06b93d..afa448bb 100644 --- a/src/pages/operator/css/KeyboardControl.css +++ b/src/pages/operator/css/KeyboardControl.css @@ -1,11 +1,16 @@ -.keyboard-control { - background-color: #f4f7f8; - margin: 1em; +/*************** MAIN KEYBOARD CONTAINER ***************/ +.keyboard-container { + position: relative; display: grid; - resize: vertical; + justify-items: center; + padding: 0.4rem; + height: 100%; + grid-template-columns: auto; + grid-template-rows: 1fr repeat(3, auto) 1fr; + justify-content: center; } -.keyboard-control.selected { +.keyboard-container.selected { opacity: 100%; stroke: var(--selected-color); opacity: 100%; @@ -15,7 +20,7 @@ pointer-events: none; } -.keyboard-control.customizing path { +.keyboard-container.customizing path { pointer-events: none; } @@ -26,22 +31,24 @@ .keyboard-column { display: flex; flex-direction: column; - justify-content: center; + justify-content: flex-end; align-items: center; margin: 0.5em; flex: 1; } -/* WASD AND ARROW KEYS LAYOUT */ +/*************** WASD AND ARROW KEYS LAYOUT ***************/ .controls-container { position: relative; display: grid; - margin: 0.5em; - grid-template-rows: repeat(3, 1fr); + margin: 1em; + grid-template-rows: repeat(3, min-content); align-items: flex-start; justify-content: center; text-align: center; flex: auto; + gap: 0.5em; + height: fit-content; } .controls-column { @@ -53,11 +60,12 @@ .gripper-container { position: relative; display: grid; - grid-template-rows: repeat(2, 1fr); + grid-template-rows: repeat(2, min-content); align-items: flex-start; justify-content: center; flex: auto; - margin: 0.5em; + margin: 1em; + gap: 2em; } .gripper-column { position: relative; @@ -65,43 +73,51 @@ align-items: center; } +div button.keyboard-button { + background-color: white; + margin: 1px; + border: 1px solid black; + height: 48px; + width: 48px; + padding: 0; +} + .keyboard-button.images { - background-color: hsl(0, 0%, 31%); - color: hsl(0, 0%, 79%); + /* background-color: white; fill-opacity: 40%; - height: 100%; + border: 1px solid black; + width: 100%; */ } -.keyboard-button.active { - fill-opacity: 60%; - background-color: hsl(200, 50%, 60%); - color: hsl(0, 0%, 79%); +.keyboard-button img { + height: 30px; + width: 30px; + object-fit: contain; } -.keyboard-button.active img { +button.keyboard-button.active { fill-opacity: 60%; background-color: hsl(200, 50%, 60%); color: hsl(0, 0%, 79%); - /* filter: brightness(50%) sepia(100%) saturate(10000%) hue-rotate(194deg); */ } .keyboard-button.collision { - background-color: orange; - fill-opacity: 40%; + background-color: rgba(255, 166, 0, 0.4); } -.keyboard-button path.limit { - background-color: red; - fill-opacity: 40%; +.keyboard-button.limit { + background-color: rgba(255, 0, 0, 0.4); } -/* BUTTON MODES */ -.mode-button { +/*************** MODE BUTTONS ***************/ +div button.mode-button { + background-color: white; stroke-linecap: round; stroke-linejoin: round; position: relative; margin-top: 0.1em; margin-inline: 0.1em; + border: 1px solid black; } .mode-button.active { @@ -109,7 +125,7 @@ color: hsl(0, 0%, 79%); } -/* TOGGLE BUTTONS */ +/*************** TOGGLE BUTTONS ***************/ .toggle-row { display: flex; } @@ -134,16 +150,15 @@ .toggle-icons { background-color: var(--selected-color); color: black; - margin-top: 0.1em; } -/* TODO: MAKE LAYOUT REACTIVE TO SCREEN SIZE */ -/* @media screen and (orientation: portrait) { +/* TODO: MAKE LAYOUT REACTIVE TO SCREEN SIZE + @media screen and (orientation: portrait) { .keyboard-control { width: 100%; } -} */ +} -/* button { +button { background-color: hsl(0, 0%, 31%); } */ diff --git a/src/pages/operator/tsx/Operator.tsx b/src/pages/operator/tsx/Operator.tsx index 868a1f22..adf270d6 100644 --- a/src/pages/operator/tsx/Operator.tsx +++ b/src/pages/operator/tsx/Operator.tsx @@ -80,7 +80,7 @@ export const Operator = (props: { setButtonStateMapRerender(!buttonStateMapRerender); } buttonFunctionProvider.setOperatorCallback(operatorCallback); - keyboardFunctionProvider.setOperatorCallback(operatorCallback); + // keyboardFunctionProvider.setOperatorCallback(operatorCallback); // Just used as a flag to force the operator to rerender when the tablet orientation // changes. diff --git a/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx index 3ab66f9b..6c124825 100644 --- a/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx +++ b/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx @@ -74,15 +74,15 @@ export enum ButtonState { /** Mapping from each type of button pad button to the state for that button */ export type ButtonStateMap = Map; +export const buttonStateMap: ButtonStateMap = new Map< + ButtonPadButton, + ButtonState +>(); + /** * Provides functions for the button pads */ export class ButtonFunctionProvider extends FunctionProvider { - private buttonStateMap: ButtonStateMap = new Map< - ButtonPadButton, - ButtonState - >(); - /** * Callback function to update the button state map in the operator so it * can rerender the button pads. @@ -123,17 +123,17 @@ export class ButtonFunctionProvider extends FunctionProvider { : buttons.reverse(); // TODO: i think there's still something wrong with this logic - const prevButtonStateNeg = this.buttonStateMap.get(buttonNeg); - const prevButtonStatePos = this.buttonStateMap.get(buttonPos); + const prevButtonStateNeg = buttonStateMap.get(buttonNeg); + const prevButtonStatePos = buttonStateMap.get(buttonPos); const prevInCollisionNeg = prevButtonStateNeg === ButtonState.Collision; const prevInCollisionPos = prevButtonStatePos === ButtonState.Collision; if (!prevButtonStateNeg || inCollisionNeg !== prevInCollisionNeg) - this.buttonStateMap.set( + buttonStateMap.set( buttonNeg, inCollisionNeg ? ButtonState.Collision : ButtonState.Inactive, ); if (!prevButtonStatePos || inCollisionPos !== prevInCollisionPos) - this.buttonStateMap.set( + buttonStateMap.set( buttonPos, inCollisionPos ? ButtonState.Collision : ButtonState.Inactive, ); @@ -148,23 +148,23 @@ export class ButtonFunctionProvider extends FunctionProvider { const buttons = getButtonsFromJointName(key); if (!buttons) return; const [buttonNeg, buttonPos] = buttons; - const prevButtonStateNeg = this.buttonStateMap.get(buttonNeg); - const prevButtonStatePos = this.buttonStateMap.get(buttonPos); + const prevButtonStateNeg = buttonStateMap.get(buttonNeg); + const prevButtonStatePos = buttonStateMap.get(buttonPos); const prevInLimitNeg = prevButtonStateNeg !== ButtonState.Limit; const prevInLimitPos = prevButtonStatePos !== ButtonState.Limit; if (prevButtonStateNeg == undefined || inLimitNeg !== prevInLimitNeg) - this.buttonStateMap.set( + buttonStateMap.set( buttonNeg, inLimitNeg ? ButtonState.Inactive : ButtonState.Limit, ); if (prevButtonStatePos == undefined || inLimitPos !== prevInLimitPos) - this.buttonStateMap.set( + buttonStateMap.set( buttonPos, inLimitPos ? ButtonState.Inactive : ButtonState.Limit, ); }); - if (this.operatorCallback) this.operatorCallback(this.buttonStateMap); + if (this.operatorCallback) this.operatorCallback(buttonStateMap); } /** @@ -185,7 +185,7 @@ export class ButtonFunctionProvider extends FunctionProvider { * @param buttonType the button pad button to set active */ private setButtonActiveState(buttonType: ButtonPadButton) { - const currentState = this.buttonStateMap.get(buttonType); + const currentState = buttonStateMap.get(buttonType); // Don't set to active if in collision or at it's limit if ( @@ -194,8 +194,8 @@ export class ButtonFunctionProvider extends FunctionProvider { ) return; - this.buttonStateMap.set(buttonType, ButtonState.Active); - if (this.operatorCallback) this.operatorCallback(this.buttonStateMap); + buttonStateMap.set(buttonType, ButtonState.Active); + if (this.operatorCallback) this.operatorCallback(buttonStateMap); } /** @@ -204,7 +204,7 @@ export class ButtonFunctionProvider extends FunctionProvider { * @param buttonType the button pad button to set active */ private setButtonInactiveState(buttonType: ButtonPadButton) { - const currentState = this.buttonStateMap.get(buttonType); + const currentState = buttonStateMap.get(buttonType); // Don't set to inactive if in collision or at it's limit if ( @@ -214,8 +214,8 @@ export class ButtonFunctionProvider extends FunctionProvider { ) return; - this.buttonStateMap.set(buttonType, ButtonState.Inactive); - if (this.operatorCallback) this.operatorCallback(this.buttonStateMap); + buttonStateMap.set(buttonType, ButtonState.Inactive); + if (this.operatorCallback) this.operatorCallback(buttonStateMap); } /** diff --git a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx index 05655a5c..0ca86ac8 100644 --- a/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx +++ b/src/pages/operator/tsx/function_providers/KeyboardFunctionProvider.tsx @@ -5,6 +5,7 @@ import { ButtonPadButton, ButtonFunctions } from "./ButtonFunctionProvider"; import { ActionMode } from "../utils/component_definitions"; //import { keyState } from "../layout_components/KeyboardControl" import { ConsoleView } from "react-device-detect"; +import { buttonStateMap, ButtonStateMap } from "./ButtonFunctionProvider"; export enum Mode { Base = "Base", @@ -13,17 +14,14 @@ export enum Mode { } export class KeyboardFunctionProvider extends FunctionProvider { + private operatorCallback?: (buttonStateMap: ButtonStateMap) => void = + undefined; + constructor() { super(); this.provideFunctions = this.provideFunctions.bind(this); } - public setOperatorCallback( - callback: (buttonStateMap: ButtonStateMap) => void, - ) { - this.operatorCallback = callback; - } - private getButtonPadButton( KeyboardFunction: Mode, key: string, @@ -160,10 +158,12 @@ export class KeyboardFunctionProvider extends FunctionProvider { key: string, ): ButtonFunctions | null { const button = this.getButtonPadButton(modeFunction, key); + const currentState = buttonStateMap.get(button); console.log( "**********************", `${button}`, - "************************", + "**********************", + `${currentState}`, ); return buttonFunctionProvider.provideFunctions(button); } diff --git a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx index 727e8d74..91d9b5e6 100644 --- a/src/pages/operator/tsx/layout_components/KeyboardControl.tsx +++ b/src/pages/operator/tsx/layout_components/KeyboardControl.tsx @@ -10,13 +10,19 @@ import "operator/css/ButtonGrid.css"; import "operator/css/KeyboardControl.css"; import { className } from "shared/util"; import { buttonFunctionProvider } from "../index"; -import { SingleButton } from "./ButtonPad"; import { ButtonState, ButtonPadButton, } from "../function_providers/ButtonFunctionProvider"; import { getIcon } from "../utils/svg"; +import { + buttonStateMap, + ButtonStateMap, +} from "../function_providers/ButtonFunctionProvider"; +/** + * Each of the possible keys that could be pressed + */ export enum PossibleKeys { w = "w", a = "a", @@ -55,6 +61,9 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { } : {}; + /** + * The state of each keyboard button, allowing the input to only run + */ const [keyState, setKeyState] = React.useState({ w: false, a: false, @@ -70,6 +79,9 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { ArrowRight: false, }); + /** + * Button pad buttons for each input for rendering the correct icon for the button + */ const buttonImages = { [Mode.Base]: { w: ButtonPadButton.BaseForward, @@ -86,11 +98,16 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { [Mode.Wrist]: { w: ButtonPadButton.WristPitchUp, a: ButtonPadButton.WristRotateIn, - s: ButtonPadButton.WristRotateOut, - d: ButtonPadButton.WristPitchDown, + s: ButtonPadButton.WristPitchDown, + d: ButtonPadButton.WristRotateOut, + q: ButtonPadButton.WristRollLeft, + e: ButtonPadButton.WristRollRight, }, }; + /** + * Toggle logic for keyboard inputs and showing SVGs + */ const toggleButton = () => { setToggleState((prevToggleState) => !prevToggleState); console.log("keys on?", toggleState); @@ -103,9 +120,6 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { setToggleSVG((prevToggleSVG) => !prevToggleSVG); }; - const buttonState: ButtonState = - props.sharedState.buttonStateMap?.get(props.funct) || ButtonState.Inactive; - const releaseAllKeys = () => { Object.keys(keyState).forEach((key) => { if (keyState[key]) { @@ -113,6 +127,7 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { } }); }; + const handleKeyPress = React.useCallback( (event) => { if (keyPressed === true) { @@ -145,10 +160,9 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { } else { console.log("Unexpected key pressed", event.key); } - setKeyState((prevState) => ({ ...prevState, [event.key]: true })); }, - [mode, keyPressed, ButtonState], + [mode, keyPressed], ); const handleKeyRelease = React.useCallback( @@ -159,7 +173,7 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { let functs = keyboardFunctionProvider.provideFunctions(mode, event.key); functs.onRelease(); }, - [mode, keyPressed, ButtonState], + [mode, keyPressed], ); React.useEffect(() => { @@ -179,9 +193,10 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { return (
+
@@ -203,21 +218,21 @@ export const KeyboardControl = (props: CustomizableComponentProps) => {
)} {activeMode === 3 && (
@@ -335,7 +319,7 @@ export const KeyboardControl = (props: CustomizableComponentProps) => { Camera Controls
+
); };