diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..0a02bcef
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "tabWidth": 4
+}
diff --git a/package.json b/package.json
index d88a880a..e675d876 100644
--- a/package.json
+++ b/package.json
@@ -1,77 +1,77 @@
{
- "name": "stretch_web_teleop",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@types/createjs": "^0.0.29",
- "@types/react": "^18.0.34",
- "@types/react-dom": "^18.0.11",
- "@types/roslib": "^1.3.0",
- "browser": "^0.2.6",
- "buffer": "^6.0.3",
- "createjs-module": "^0.8.3",
- "css-loader": "^6.7.3",
- "dotenv": "^16.0.3",
- "eslint-config-react-app": "^7.0.1",
- "express": "^4.18.2",
- "file-loader": "^6.2.0",
- "firebase": "^9.22.1",
- "html-webpack-plugin": "^4.5.2",
- "jpeg-js": "^0.4.4",
- "latest-createjs": "^1.0.24",
- "nodejs": "^0.0.0",
- "nodemon": "^3.0.1",
- "playwright": "^1.33.0",
- "pm2": "^5.3.0",
- "process": "^0.11.10",
- "react": "^18.2.0",
- "react-device-detect": "^2.2.3",
- "react-dom": "^18.2.0",
- "react-native-keyboard-aware-scrollview": "^2.1.0",
- "react-native-web": "^0.19.7",
- "react-path-tooltip": "^1.0.25",
- "roslib": "github:hello-vinitha/roslibjs#ros2actionclient",
- "socket.io": "^4.6.1",
- "socket.io-client": "^4.6.1",
- "style-loader": "^3.3.2",
- "three": "^0.157.0"
- },
- "scripts": {
- "firebase": "webpack --mode development --progress --env storage='firebase'",
- "localstorage": "webpack --mode development --progress --env storage='localstorage'",
- "styleguide": "styleguidist server"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- },
- "devDependencies": {
- "@babel/plugin-transform-runtime": "^7.21.4",
- "@babel/preset-env": "^7.22.6",
- "@babel/preset-react": "^7.18.6",
- "@babel/preset-typescript": "^7.21.4",
- "@types/node": "^20.6.0",
- "@types/three": "^0.157.0",
- "babel-loader": "^9.1.2",
- "babel-polyfill": "^6.26.0",
- "react-docgen-typescript": "^2.2.2",
- "react-styleguidist": "^13.1.1",
- "typescript": "^5.2.2",
- "webpack": "^5.88.2",
- "webpack-cli": "^5.0.2"
- }
+ "name": "stretch_web_teleop",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@types/createjs": "^0.0.29",
+ "@types/react": "^18.0.34",
+ "@types/react-dom": "^18.0.11",
+ "@types/roslib": "^1.3.0",
+ "browser": "^0.2.6",
+ "buffer": "^6.0.3",
+ "createjs-module": "^0.8.3",
+ "css-loader": "^6.7.3",
+ "dotenv": "^16.0.3",
+ "eslint-config-react-app": "^7.0.1",
+ "express": "^4.18.2",
+ "file-loader": "^6.2.0",
+ "firebase": "^9.22.1",
+ "html-webpack-plugin": "^4.5.2",
+ "jpeg-js": "^0.4.4",
+ "latest-createjs": "^1.0.24",
+ "nodejs": "^0.0.0",
+ "nodemon": "^3.0.1",
+ "playwright": "^1.33.0",
+ "pm2": "^5.3.0",
+ "process": "^0.11.10",
+ "react": "^18.2.0",
+ "react-device-detect": "^2.2.3",
+ "react-dom": "^18.2.0",
+ "react-native-keyboard-aware-scrollview": "^2.1.0",
+ "react-native-web": "^0.19.7",
+ "react-path-tooltip": "^1.0.25",
+ "roslib": "github:hello-vinitha/roslibjs#ros2actionclient",
+ "socket.io": "^4.6.1",
+ "socket.io-client": "^4.6.1",
+ "style-loader": "^3.3.2",
+ "three": "^0.157.0"
+ },
+ "scripts": {
+ "firebase": "webpack --mode development --progress --env storage='firebase'",
+ "localstorage": "webpack --mode development --progress --env storage='localstorage'",
+ "styleguide": "styleguidist server"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@babel/plugin-transform-runtime": "^7.21.4",
+ "@babel/preset-env": "^7.22.6",
+ "@babel/preset-react": "^7.18.6",
+ "@babel/preset-typescript": "^7.21.4",
+ "@types/node": "^20.6.0",
+ "@types/three": "^0.157.0",
+ "babel-loader": "^9.1.2",
+ "babel-polyfill": "^6.26.0",
+ "react-docgen-typescript": "^2.2.2",
+ "react-styleguidist": "^13.1.1",
+ "typescript": "^5.2.2",
+ "webpack": "^5.88.2",
+ "webpack-cli": "^5.0.2"
+ }
}
diff --git a/server.js b/server.js
index f825c677..9fccbe47 100644
--- a/server.js
+++ b/server.js
@@ -2,8 +2,8 @@ var fs = require("fs");
require("dotenv").config();
var options = {
- key: fs.readFileSync(`certificates/${process.env.keyfile}`),
- cert: fs.readFileSync(`certificates/${process.env.certfile}`),
+ key: fs.readFileSync(`certificates/${process.env.keyfile}`),
+ cert: fs.readFileSync(`certificates/${process.env.certfile}`),
};
// const http = require('http');
@@ -25,20 +25,20 @@ var app = express();
app.all("*", ensureSecure); // at top of routing calls
function ensureSecure(req, res, next) {
- if (!req.secure) {
- // handle port numbers if you need non defaults
- console.log("redirecting insecure request");
- return res.redirect("https://" + req.hostname + req.url);
- // res.redirect(`https://${req.hostname}${process.env.NGROK_URL}`);
- }
+ if (!req.secure) {
+ // handle port numbers if you need non defaults
+ console.log("redirecting insecure request");
+ return res.redirect("https://" + req.hostname + req.url);
+ // res.redirect(`https://${req.hostname}${process.env.NGROK_URL}`);
+ }
- return next();
+ return next();
}
var server = require("http").Server(app);
var secure_server = require("https").Server(options, app);
const io = socket(secure_server, {
- allowEIO3: true,
+ allowEIO3: true,
});
app.enable("trust proxy");
app.set("port", 443);
@@ -51,63 +51,65 @@ app.use("/", express.static(path.join(__dirname, "dist")));
app.listen(process.env.port);
io.on("connect_error", (err) => {
- console.log(`connect_error due to ${err.message}`);
+ console.log(`connect_error due to ${err.message}`);
});
io.on("connection", function (socket) {
- console.log("new socket.io connection");
- // console.log('socket.handshake = ');
- // console.log(socket.handshake);
+ console.log("new socket.io connection");
+ // console.log('socket.handshake = ');
+ // console.log(socket.handshake);
- socket.on("join", function (room) {
- console.log("Received request to join room " + room);
- // A room can have atmost two clients
- if (
- !io.sockets.adapter.rooms.get(room) ||
- io.sockets.adapter.rooms.get(room).size < 2
- ) {
- socket.join(room);
- socket.emit("join", room, socket.id);
- } else {
- console.log("room full");
- socket.emit("full", room);
- }
- });
+ socket.on("join", function (room) {
+ console.log("Received request to join room " + room);
+ // A room can have atmost two clients
+ if (
+ !io.sockets.adapter.rooms.get(room) ||
+ io.sockets.adapter.rooms.get(room).size < 2
+ ) {
+ socket.join(room);
+ socket.emit("join", room, socket.id);
+ } else {
+ console.log("room full");
+ socket.emit("full", room);
+ }
+ });
- socket.on("add operator to robot room", (callback) => {
- // The robot room is only available if another operator is not connected to it
- if (io.sockets.adapter.rooms.get("robot")) {
- if (io.sockets.adapter.rooms.get("robot").size < 2) {
- socket.join("robot");
- socket.in("robot").emit("joined", "robot");
- callback({ success: true });
- } else {
- console.log("could not connect because robot room is full");
- callback({ success: false });
- }
- } else {
- console.log("could not connect because robot is not available");
- callback({ success: false });
- }
- });
+ socket.on("add operator to robot room", (callback) => {
+ // The robot room is only available if another operator is not connected to it
+ if (io.sockets.adapter.rooms.get("robot")) {
+ if (io.sockets.adapter.rooms.get("robot").size < 2) {
+ socket.join("robot");
+ socket.in("robot").emit("joined", "robot");
+ callback({ success: true });
+ } else {
+ console.log("could not connect because robot room is full");
+ callback({ success: false });
+ }
+ } else {
+ console.log("could not connect because robot is not available");
+ callback({ success: false });
+ }
+ });
- socket.on("signalling", function (message) {
- if (io.sockets.adapter.rooms.get("robot")) {
- socket.to("robot").emit("signalling", message);
- } else {
- console.log(
- "robot_operator_room is none, so there is nobody to send the WebRTC message to",
- );
- }
- });
+ socket.on("signalling", function (message) {
+ if (io.sockets.adapter.rooms.get("robot")) {
+ socket.to("robot").emit("signalling", message);
+ } else {
+ console.log(
+ "robot_operator_room is none, so there is nobody to send the WebRTC message to",
+ );
+ }
+ });
- socket.on("bye", (role) => {
- console.log(role, socket.rooms);
- if (socket.rooms.has("robot")) {
- socket.to("robot").emit("bye");
- console.log("Attempting to have the " + role + " leave the robot room.");
- console.log("");
- socket.leave("robot");
- }
- });
+ socket.on("bye", (role) => {
+ console.log(role, socket.rooms);
+ if (socket.rooms.has("robot")) {
+ socket.to("robot").emit("bye");
+ console.log(
+ "Attempting to have the " + role + " leave the robot room.",
+ );
+ console.log("");
+ socket.leave("robot");
+ }
+ });
});
diff --git a/src/pages/operator/css/Alert.css b/src/pages/operator/css/Alert.css
index 1ec72aa2..61eacd5f 100644
--- a/src/pages/operator/css/Alert.css
+++ b/src/pages/operator/css/Alert.css
@@ -1,65 +1,65 @@
/* The alert message box */
.alert {
- position: relative;
- padding: 2rem 1rem;
- /* margin-bottom: 1rem; */
- border: 1px solid transparent;
- border-radius: 0.25rem;
- height: 5.5rem;
- text-align: center;
+ position: relative;
+ padding: 2rem 1rem;
+ /* margin-bottom: 1rem; */
+ border: 1px solid transparent;
+ border-radius: 0.25rem;
+ height: 5.5rem;
+ text-align: center;
}
.error {
- color: #842029;
- background-color: #f8d7da;
- border-color: #f5c2c7;
+ color: #842029;
+ background-color: #f8d7da;
+ border-color: #f5c2c7;
}
.success {
- color: #0f4f51;
- background-color: #d1e4e7;
- border-color: #badbcc;
+ color: #0f4f51;
+ background-color: #d1e4e7;
+ border-color: #badbcc;
}
.warning {
- color: #664d03;
- background-color: #fff3cd;
- border-color: #ffecb5;
+ color: #664d03;
+ background-color: #fff3cd;
+ border-color: #ffecb5;
}
.primary {
- color: #084298;
- background-color: #cfe2ff;
- border-color: #b6d4fe;
+ color: #084298;
+ background-color: #cfe2ff;
+ border-color: #b6d4fe;
}
.info {
- color: #41464b;
- background-color: #e2e3e5;
- border-color: #d3d6d8;
+ color: #41464b;
+ background-color: #e2e3e5;
+ border-color: #d3d6d8;
}
.secondary {
- color: #055160;
- background-color: #cff4fc;
- border-color: #b6effb;
+ color: #055160;
+ background-color: #cff4fc;
+ border-color: #b6effb;
}
/* The close button */
.closebtn {
- margin-left: 15px;
- /* color: white; */
- font-weight: bold;
- float: right;
- font-size: 3rem;
- line-height: 0.75rem;
- cursor: pointer;
- transition: 0.3s;
+ margin-left: 15px;
+ /* color: white; */
+ font-weight: bold;
+ float: right;
+ font-size: 3rem;
+ line-height: 0.75rem;
+ cursor: pointer;
+ transition: 0.3s;
}
/* When moving the mouse over the close button */
.closebtn:hover {
- color: black;
+ color: black;
}
.hide {
- display: none;
+ display: none;
}
diff --git a/src/pages/operator/css/AudioControl.css b/src/pages/operator/css/AudioControl.css
index 21d2be09..b001a321 100644
--- a/src/pages/operator/css/AudioControl.css
+++ b/src/pages/operator/css/AudioControl.css
@@ -1,15 +1,15 @@
.audioControlContainer {
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
}
.audioControl {
- display: none;
+ display: none;
}
.audioControlButton {
- border-radius: 50%;
- aspect-ratio: 1;
+ border-radius: 50%;
+ aspect-ratio: 1;
}
diff --git a/src/pages/operator/css/BatteryGuage.css b/src/pages/operator/css/BatteryGuage.css
index 623dff3d..c6985969 100644
--- a/src/pages/operator/css/BatteryGuage.css
+++ b/src/pages/operator/css/BatteryGuage.css
@@ -1,59 +1,59 @@
.bar {
- width: 100%;
- height: 15px;
- background-color: #436fb7;
- display: flex;
- margin-top: 3px;
+ width: 100%;
+ height: 15px;
+ background-color: #436fb7;
+ display: flex;
+ margin-top: 3px;
}
.barsContainer {
- width: 50px;
- height: 100px;
- display: flex;
- flex-direction: column-reverse;
- align-items: center;
+ width: 50px;
+ height: 100px;
+ display: flex;
+ flex-direction: column-reverse;
+ align-items: center;
}
.batteryGauge {
- height: 100px;
- /* position: absolute; */
- width: 100%;
- padding: 0px 10px 10px 10px;
+ height: 100px;
+ /* position: absolute; */
+ width: 100%;
+ padding: 0px 10px 10px 10px;
}
.green {
- filter: invert(17%) sepia(15%) saturate(6070%) hue-rotate(89deg)
- brightness(75%) contrast(108%);
+ filter: invert(17%) sepia(15%) saturate(6070%) hue-rotate(89deg)
+ brightness(75%) contrast(108%);
}
.yellow-green {
- filter: brightness(70%) saturate(100%) invert(0%) sepia(28%) saturate(4155%)
- hue-rotate(56deg) contrast(119%);
+ filter: brightness(70%) saturate(100%) invert(0%) sepia(28%) saturate(4155%)
+ hue-rotate(56deg) contrast(119%);
}
.yellow {
- filter: brightness(90%) saturate(100%) invert(0%) sepia(28%) saturate(4155%)
- hue-rotate(6deg) contrast(119%);
+ filter: brightness(90%) saturate(100%) invert(0%) sepia(28%) saturate(4155%)
+ hue-rotate(6deg) contrast(119%);
}
.orange {
- filter: brightness(70%) saturate(100%) invert(10%) sepia(28%) saturate(4155%)
- hue-rotate(6deg) contrast(119%);
+ filter: brightness(70%) saturate(100%) invert(10%) sepia(28%)
+ saturate(4155%) hue-rotate(6deg) contrast(119%);
}
.orange-red {
- filter: brightness(50%) saturate(100%) invert(10%) sepia(28%) saturate(4155%)
- hue-rotate(6deg) contrast(119%);
+ filter: brightness(50%) saturate(100%) invert(10%) sepia(28%)
+ saturate(4155%) hue-rotate(6deg) contrast(119%);
}
.red {
- filter: brightness(30%) saturate(100%) invert(10%) sepia(28%) saturate(4155%)
- hue-rotate(346deg) contrast(119%);
+ filter: brightness(30%) saturate(100%) invert(10%) sepia(28%)
+ saturate(4155%) hue-rotate(346deg) contrast(119%);
}
.batteryGaugeContainer {
- align-items: center;
- display: flex;
- flex-direction: column;
- max-height: 90%;
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ max-height: 90%;
}
diff --git a/src/pages/operator/css/ButtonGrid.css b/src/pages/operator/css/ButtonGrid.css
index 97742c87..e5e70e30 100644
--- a/src/pages/operator/css/ButtonGrid.css
+++ b/src/pages/operator/css/ButtonGrid.css
@@ -1,58 +1,58 @@
.button-grid {
- flex: 2;
- display: grid;
- grid-template-columns: repeat(6, 1fr);
- grid-template-rows: repeat(4, auto 1fr);
- grid-template-areas:
- "header0 header0 header0 header0 header0 header0"
- ". b0 b1 b2 b3 ."
- "header1 header1 header1 header1 header1 header1"
- ". b4 b5 b6 b7 ."
- "header2 header2 header2 header2 header2 header2"
- "b8 b9 b10 b11 b12 b13"
- "header3 header3 header3 header3 header3 header3"
- ". . b14 b15 . .";
-
- padding: 0.4rem;
- position: relative;
- z-index: 1;
- border-radius: var(--radius);
+ flex: 2;
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ grid-template-rows: repeat(4, auto 1fr);
+ grid-template-areas:
+ "header0 header0 header0 header0 header0 header0"
+ ". b0 b1 b2 b3 ."
+ "header1 header1 header1 header1 header1 header1"
+ ". b4 b5 b6 b7 ."
+ "header2 header2 header2 header2 header2 header2"
+ "b8 b9 b10 b11 b12 b13"
+ "header3 header3 header3 header3 header3 header3"
+ ". . b14 b15 . .";
+
+ padding: 0.4rem;
+ position: relative;
+ z-index: 1;
+ border-radius: var(--radius);
}
.button-grid::after {
- border-radius: var(--radius);
+ border-radius: var(--radius);
}
.button-grid p {
- text-align: center;
- margin: 1.5rem 0 0.5rem 0;
+ text-align: center;
+ margin: 1.5rem 0 0.5rem 0;
}
.button-grid p:first-of-type {
- text-align: center;
- margin-top: 0.5rem;
+ text-align: center;
+ margin-top: 0.5rem;
}
.button-grid button {
- width: 90%;
- height: 4rem;
- justify-self: center;
- align-self: center;
+ width: 90%;
+ height: 4rem;
+ justify-self: center;
+ align-self: center;
}
.button-grid button.active {
- background-color: var(--btn-turquoise);
+ background-color: var(--btn-turquoise);
}
.button-grid button.collision {
- background-color: orange;
+ background-color: orange;
}
.button-grid button.limit {
- background-color: red;
+ background-color: red;
}
.button-grid-bkg-color {
- grid-column: 1 / 7;
- height: 5rem;
+ grid-column: 1 / 7;
+ height: 5rem;
}
diff --git a/src/pages/operator/css/ButtonPad.css b/src/pages/operator/css/ButtonPad.css
index 3829a6f0..a2cd6874 100644
--- a/src/pages/operator/css/ButtonPad.css
+++ b/src/pages/operator/css/ButtonPad.css
@@ -1,117 +1,117 @@
/* Stand-alone button pad *****************************************************/
.button-pads {
- /* Shared style */
- stroke-linecap: round;
- stroke-linejoin: round;
- cursor: pointer;
-
- /* Style for standalone version (not overlay */
- fill-opacity: 100%;
- stroke-width: 6px;
- stroke: var(--background-color);
- fill: hsl(0, 0%, 31%);
- flex: 1 1 0;
- padding: 0.4rem;
- max-height: 100%;
- /* max-width: fit-content; */
- touch-action: none;
- user-select: none;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ /* Shared style */
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ cursor: pointer;
+
+ /* Style for standalone version (not overlay */
+ fill-opacity: 100%;
+ stroke-width: 6px;
+ stroke: var(--background-color);
+ fill: hsl(0, 0%, 31%);
+ flex: 1 1 0;
+ padding: 0.4rem;
+ max-height: 100%;
+ /* max-width: fit-content; */
+ touch-action: none;
+ user-select: none;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.button-pads svg {
- height: 100%;
- width: 100%;
+ height: 100%;
+ width: 100%;
}
/* Overlay ********************************************************************/
.button-pads.overlay {
- stroke-width: 3px;
- fill: hsl(200, 50%, 60%);
- fill-opacity: 0;
- stroke: hsl(200, 0%, 60%);
+ stroke-width: 3px;
+ fill: hsl(200, 50%, 60%);
+ fill-opacity: 0;
+ stroke: hsl(200, 0%, 60%);
- width: 100%;
- height: 100%;
- padding: 0;
- margin: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
}
/* Settings for the buttons on the button pad *********************************/
/*When hovering over a child element of an svg*/
.button-pads path:hover {
- fill-opacity: 30%;
+ fill-opacity: 30%;
}
@media (hover: none) {
- .button-pads path.inactive:hover + image {
- filter: none !important;
- }
+ .button-pads path.inactive:hover + image {
+ filter: none !important;
+ }
}
.button-pads image {
- pointer-events: none;
+ pointer-events: none;
}
.button-pads path.inactive:hover + image {
- filter: brightness(50%) sepia(100%) saturate(10000%) hue-rotate(194deg);
+ filter: brightness(50%) sepia(100%) saturate(10000%) hue-rotate(194deg);
}
.button-pads path.active {
- fill-opacity: 60%;
- fill: var(--btn-turquoise);
+ fill-opacity: 60%;
+ fill: var(--btn-turquoise);
}
.button-pads path.collision {
- fill: orange;
- fill-opacity: 40%;
+ fill: orange;
+ fill-opacity: 40%;
}
.button-pads path.limit {
- fill: red;
- fill-opacity: 40%;
+ fill: red;
+ fill-opacity: 40%;
}
/* Disable hover actions */
.button-pads.customizing path {
- pointer-events: none;
+ pointer-events: none;
}
.button-pads.selected {
- opacity: 100%;
- stroke: var(--selected-color);
- opacity: 100%;
- border: 1px solid var(--selected-color);
- stroke-width: 5px;
+ opacity: 100%;
+ stroke: var(--selected-color);
+ opacity: 100%;
+ border: 1px solid var(--selected-color);
+ stroke-width: 5px;
}
.button-pad {
- font-size: x-large;
- flex: 1 1 0;
- display: grid;
- text-align: center;
- max-height: 100%;
- justify-items: center;
+ font-size: x-large;
+ flex: 1 1 0;
+ display: grid;
+ text-align: center;
+ max-height: 100%;
+ justify-items: center;
}
@media (max-width: 1300px) {
- .title {
- font-size: smaller;
- margin: 0.5rem;
- }
+ .title {
+ font-size: smaller;
+ margin: 0.5rem;
+ }
}
@media screen and (orientation: portrait) {
- .button-pad {
- width: 100%;
- }
+ .button-pad {
+ width: 100%;
+ }
}
@media (max-width: 500px) {
- .button-pads {
- filter: drop-shadow(6px 7px 2px rgb(0 0 0 / 0.4));
- }
+ .button-pads {
+ filter: drop-shadow(6px 7px 2px rgb(0 0 0 / 0.4));
+ }
}
diff --git a/src/pages/operator/css/CameraView.css b/src/pages/operator/css/CameraView.css
index 822a5caa..df5f30f2 100644
--- a/src/pages/operator/css/CameraView.css
+++ b/src/pages/operator/css/CameraView.css
@@ -1,135 +1,135 @@
.video-container {
- position: relative;
- flex: 1 1 0;
- display: grid;
- justify-items: center;
- padding: 0.4rem;
- height: 100%;
- grid-template-columns: auto;
- grid-template-rows: 1fr auto minmax(15rem, 2fr);
- object-fit: cover;
- justify-content: center;
- /* align-items: center;
+ position: relative;
+ flex: 1 1 0;
+ display: grid;
+ justify-items: center;
+ padding: 0.4rem;
+ height: 100%;
+ grid-template-columns: auto;
+ grid-template-rows: 1fr auto minmax(15rem, 2fr);
+ object-fit: cover;
+ justify-content: center;
+ /* align-items: center;
flex-direction: column; */
}
/* Overlays the button pad on top of the camera view*/
.video-overlay-container {
- z-index: 1;
- position: absolute;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- top: 0;
- left: 0;
+ z-index: 1;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
}
.video-overlay-container.realsense,
.video-overlay-container.overhead {
- z-index: 2;
- position: absolute;
- width: calc(100% - 102px);
- height: calc(100% - 102px);
- box-sizing: border-box;
- top: 0;
- left: 0;
- left: 51px;
- top: 51px;
- display: flex;
- align-items: center;
+ z-index: 2;
+ position: absolute;
+ width: calc(100% - 102px);
+ height: calc(100% - 102px);
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ left: 51px;
+ top: 51px;
+ display: flex;
+ align-items: center;
}
/* The style for the plus icon overlay for click-to-pregrasp */
.realsense.material-icons {
- color: rgba(255, 0, 0, 0.6);
- position: absolute;
- transform: translate(-50%, -50%);
+ color: rgba(255, 0, 0, 0.6);
+ position: absolute;
+ transform: translate(-50%, -50%);
}
.video-overlay-container.overhead.predictiveDisplay {
- z-index: 2;
- position: absolute;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- top: 0;
- left: 0;
- display: flex;
- align-items: center;
+ z-index: 2;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ display: flex;
+ align-items: center;
}
.video-overlay-container::after {
- grid-row: 2/2;
- grid-column: 1/1;
+ grid-row: 2/2;
+ grid-column: 1/1;
}
.video-area {
- position: relative;
- grid-row: 2/2;
- width: 100%;
- max-height: 100%;
- max-width: fit-content; /* 100% */
- object-fit: cover;
- display: inline-block;
+ position: relative;
+ grid-row: 2/2;
+ width: 100%;
+ max-height: 100%;
+ max-width: fit-content; /* 100% */
+ object-fit: cover;
+ display: inline-block;
}
.video-canvas {
- display: inline-block;
- width: 100%;
- height: auto;
- object-fit: cover;
+ display: inline-block;
+ width: 100%;
+ height: auto;
+ object-fit: cover;
}
.video-canvas.constrainedHeight {
- display: inline-block;
- /* width: auto; */
- height: 100%;
- object-fit: cover;
+ display: inline-block;
+ /* width: auto; */
+ height: 100%;
+ object-fit: cover;
}
/* Don't display selected highlight on video canvas element */
.video-canvas.customizing::after {
- content: none;
+ content: none;
}
.video-canvas.customizing {
- filter: brightness(0.6);
+ filter: brightness(0.6);
}
.video-canvas.customizing.selected {
- filter: none;
+ filter: none;
}
/* Under video buttons ********************************************************/
.under-video-area {
- grid-row: 3;
- grid-column: 1;
- /* background: blue; */
- /* display: grid; */
- /* flex-wrap: wrap; */
- align-content: flex-start;
- padding-top: 0.5rem;
- width: 100%;
- object-fit: cover;
- /* justify-content: center; */
- align-items: center;
- overflow-y: auto;
- overflow-x: hidden;
+ grid-row: 3;
+ grid-column: 1;
+ /* background: blue; */
+ /* display: grid; */
+ /* flex-wrap: wrap; */
+ align-content: flex-start;
+ padding-top: 0.5rem;
+ width: 100%;
+ object-fit: cover;
+ /* justify-content: center; */
+ align-items: center;
+ overflow-y: auto;
+ overflow-x: hidden;
}
.under-video-area button {
- /* height: 30%; */
- margin: 0.2rem 0rem 0.2rem 0rem;
- /* flex: 1 0 auto; */
- align-items: center;
- width: 99%;
+ /* height: 30%; */
+ margin: 0.2rem 0rem 0.2rem 0rem;
+ /* flex: 1 0 auto; */
+ align-items: center;
+ width: 99%;
}
/* Realsense pan-tilt controls ************************************************/
.realsense-pan-tilt-grid {
- width: 100%;
- /* max-width: fit-content;
+ width: 100%;
+ /* max-width: fit-content;
height: auto;
grid-row: 2/2;
grid-column: 1/1;
@@ -142,124 +142,124 @@
}
.realsense-pan-tilt-grid.constrainedHeight {
- height: 100%;
- /* width: auto; */
- /* grid-template-columns: var(--pan-tilt-button-size) min-content var(--pan-tilt-button-size);
+ height: 100%;
+ /* width: auto; */
+ /* grid-template-columns: var(--pan-tilt-button-size) min-content var(--pan-tilt-button-size);
grid-template-rows: var(--pan-tilt-button-size) fit-content var(--pan-tilt-button-size); */
- justify-content: center;
- object-fit: cover;
- z-index: 2;
- position: absolute;
- display: block;
+ justify-content: center;
+ object-fit: cover;
+ z-index: 2;
+ position: absolute;
+ display: block;
}
.realsense-pan-tilt-grid button {
- padding: 0px;
+ padding: 0px;
}
.realsense-pan-tilt-grid button .material-icons {
- margin: 0;
- font-size: xxx-large;
- background-color: whitesmoke;
- opacity: 40%;
- border-radius: 60px;
+ margin: 0;
+ font-size: xxx-large;
+ background-color: whitesmoke;
+ opacity: 40%;
+ border-radius: 60px;
}
.realsense-pan-tilt-grid .up {
- border-radius: var(--btn-brdr-radius) var(--btn-brdr-radius) 0 0;
- z-index: 2;
- width: 100%;
- position: absolute;
- background: transparent;
- box-shadow: none;
+ border-radius: var(--btn-brdr-radius) var(--btn-brdr-radius) 0 0;
+ z-index: 2;
+ width: 100%;
+ position: absolute;
+ background: transparent;
+ box-shadow: none;
}
.realsense-pan-tilt-grid .down {
- border-radius: 0 0 var(--btn-brdr-radius) var(--btn-brdr-radius);
- z-index: 2;
- width: 100%;
- bottom: 0;
- left: 0;
- position: absolute;
- background: transparent;
- box-shadow: none;
+ border-radius: 0 0 var(--btn-brdr-radius) var(--btn-brdr-radius);
+ z-index: 2;
+ width: 100%;
+ bottom: 0;
+ left: 0;
+ position: absolute;
+ background: transparent;
+ box-shadow: none;
}
.realsense-pan-tilt-grid .left {
- border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
- z-index: 2;
- height: 100%;
- position: absolute;
- background: transparent;
- box-shadow: none;
+ border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
+ z-index: 2;
+ height: 100%;
+ position: absolute;
+ background: transparent;
+ box-shadow: none;
}
.realsense-pan-tilt-grid .right {
- border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
- z-index: 2;
- position: absolute;
- right: 0;
- height: 100%;
- background: transparent;
- box-shadow: none;
+ border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
+ z-index: 2;
+ position: absolute;
+ right: 0;
+ height: 100%;
+ background: transparent;
+ box-shadow: none;
}
/* Context menu popup *********************************************************/
.video-context-menu {
- list-style-type: none;
- position: absolute;
- background-color: var(--background-color);
- /* border: var(--btn-brdr); */
- margin: 0;
- padding: 0;
- white-space: nowrap;
-
- --padding: 1rem;
-
- padding-top: var(--padding);
- grid-row: 2/2;
- z-index: 4;
+ list-style-type: none;
+ position: absolute;
+ background-color: var(--background-color);
+ /* border: var(--btn-brdr); */
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+
+ --padding: 1rem;
+
+ padding-top: var(--padding);
+ grid-row: 2/2;
+ z-index: 4;
}
.video-context-menu li {
- padding: var(--padding);
- cursor: pointer;
- background-color: inherit;
+ padding: var(--padding);
+ cursor: pointer;
+ background-color: inherit;
}
.video-context-menu li:hover {
- filter: brightness(90%);
+ filter: brightness(90%);
}
.video-context-menu::before {
- content: attr(aria-label);
- font-weight: bold;
- padding: var(--padding);
+ content: attr(aria-label);
+ font-weight: bold;
+ padding: var(--padding);
}
.title {
- font-size: x-large;
- align-self: flex-end;
- text-align: center;
- margin: 10px;
+ font-size: x-large;
+ align-self: flex-end;
+ text-align: center;
+ margin: 10px;
}
@media screen and (orientation: portrait) and (max-device-width: 900px) {
- .video-container {
- grid-template-rows: 1fr auto minmax(3rem, 2fr);
- }
+ .video-container {
+ grid-template-rows: 1fr auto minmax(3rem, 2fr);
+ }
}
@media screen and (orientation: landscape) and (max-device-width: 900px) {
- .video-container {
- grid-template-rows: auto;
- }
+ .video-container {
+ grid-template-rows: auto;
+ }
- .realsense-pan-tilt-grid button .material-icons {
- font-size: larger;
- }
+ .realsense-pan-tilt-grid button .material-icons {
+ font-size: larger;
+ }
- .title {
- font-size: smaller;
- }
+ .title {
+ font-size: smaller;
+ }
}
diff --git a/src/pages/operator/css/CustomizeButton.css b/src/pages/operator/css/CustomizeButton.css
index 3b83b732..09be3cdb 100644
--- a/src/pages/operator/css/CustomizeButton.css
+++ b/src/pages/operator/css/CustomizeButton.css
@@ -1,16 +1,16 @@
#customize-button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: var(--header-btn-width);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--header-btn-width);
}
#customize-button span {
- margin-right: 2rem;
+ margin-right: 2rem;
}
@media (max-width: 900px) {
- #customize-button {
- width: var(--btn-header-width-med);
- }
+ #customize-button {
+ width: var(--btn-header-width-med);
+ }
}
diff --git a/src/pages/operator/css/DropZone.css b/src/pages/operator/css/DropZone.css
index 30dbcbee..b47d72bf 100644
--- a/src/pages/operator/css/DropZone.css
+++ b/src/pages/operator/css/DropZone.css
@@ -1,60 +1,60 @@
.drop-zone {
- background-color: hsl(0, 0%, 90%);
- border: 0.2rem dashed gray;
- border-radius: 10px;
- opacity: 100%;
- flex: 0 0 2rem;
- margin: 1rem 0.5rem;
- cursor: pointer;
- text-align: center;
- transition-property: opacity, flex, width;
- transition-duration: 0.4s;
- transition-timing-function: ease-out;
+ background-color: hsl(0, 0%, 90%);
+ border: 0.2rem dashed gray;
+ border-radius: 10px;
+ opacity: 100%;
+ flex: 0 0 2rem;
+ margin: 1rem 0.5rem;
+ cursor: pointer;
+ text-align: center;
+ transition-property: opacity, flex, width;
+ transition-duration: 0.4s;
+ transition-timing-function: ease-out;
}
.drop-zone.standard {
- height: 90%;
+ height: 90%;
}
/* Drop zone in layout or tab content*/
.drop-zone.standard,
.drop-zone.overlay {
- display: flex;
- align-items: center;
- justify-content: center;
- width: -webkit-fill-available;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: -webkit-fill-available;
}
/* Dropzone is a tab in a tabs component header */
.drop-zone.material-icons.tab {
- margin: 0;
- width: 5rem;
- padding-top: 10px;
+ margin: 0;
+ width: 5rem;
+ padding-top: 10px;
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- /* border-bottom: none; */
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ /* border-bottom: none; */
}
/* Dropzone is an overlay in a video stream*/
.drop-zone.overlay {
- margin: 0;
- width: 100%;
- height: 100%;
- opacity: 60%;
- flex-grow: 1;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 60%;
+ flex-grow: 1;
}
.drop-zone[hidden] {
- flex: 0;
- width: 0;
- opacity: 0;
- margin: 0;
- padding: 0;
- border: none;
- visibility: hidden;
+ flex: 0;
+ width: 0;
+ opacity: 0;
+ margin: 0;
+ padding: 0;
+ border: none;
+ visibility: hidden;
}
.drop-zone:hover {
- background-color: hsl(0, 0%, 50%);
+ background-color: hsl(0, 0%, 50%);
}
diff --git a/src/pages/operator/css/LayoutArea.css b/src/pages/operator/css/LayoutArea.css
index 6f68b847..da8debc9 100644
--- a/src/pages/operator/css/LayoutArea.css
+++ b/src/pages/operator/css/LayoutArea.css
@@ -1,38 +1,38 @@
#layout-area {
- /* padding: var(--screen-padding);
+ /* padding: var(--screen-padding);
flex: 1 1 0; */
- padding: var(--screen-padding);
- flex-direction: column;
- height: auto;
+ padding: var(--screen-padding);
+ flex-direction: column;
+ height: auto;
}
/* Remove margin for elements at edges of the layout area */
#layout-area > *:first-of-type {
- margin-left: 0;
+ margin-left: 0;
}
#layout-area > *:last-of-type {
- margin-right: 0;
+ margin-right: 0;
}
#layout-area,
.tabs-content {
- display: flex;
- justify-content: space-evenly;
- align-items: center;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
}
@media screen and (orientation: portrait) and (max-device-width: 900px) {
- #layout-area,
- .tabs-content {
- flex-direction: column;
- padding: 1rem;
- grid-gap: 0.3rem;
- }
+ #layout-area,
+ .tabs-content {
+ flex-direction: column;
+ padding: 1rem;
+ grid-gap: 0.3rem;
+ }
}
@media screen and (orientation: landscape) and (max-device-width: 900px) {
- #layout-area,
- .tabs-content {
- overflow: scroll;
- }
+ #layout-area,
+ .tabs-content {
+ overflow: scroll;
+ }
}
diff --git a/src/pages/operator/css/Map.css b/src/pages/operator/css/Map.css
index dbf816d5..b778c8d6 100644
--- a/src/pages/operator/css/Map.css
+++ b/src/pages/operator/css/Map.css
@@ -1,153 +1,153 @@
.map-container,
.mobile-map-container {
- position: relative;
- /* flex: 1 1 0; */
- display: grid;
- justify-items: center;
- padding: 0.4rem;
- max-height: 100%;
- grid-template-columns: auto;
- /* grid-template-rows: 1fr auto minmax(9rem, 1fr); */
- object-fit: cover;
- justify-content: center;
- /* align-items: center;
+ position: relative;
+ /* flex: 1 1 0; */
+ display: grid;
+ justify-items: center;
+ padding: 0.4rem;
+ max-height: 100%;
+ grid-template-columns: auto;
+ /* grid-template-rows: 1fr auto minmax(9rem, 1fr); */
+ object-fit: cover;
+ justify-content: center;
+ /* align-items: center;
flex-direction: column; */
- z-index: 1;
+ z-index: 1;
}
.map-container {
- flex: 1 1 0;
- /* display: flex;
+ flex: 1 1 0;
+ /* display: flex;
flex-direction: column; */
- /* overflow: hidden; */
+ /* overflow: hidden; */
}
.map-title {
- font-size: x-large;
- text-align: center;
- margin: 10px;
+ font-size: x-large;
+ text-align: center;
+ margin: 10px;
}
.map {
- align-self: center;
- width: auto;
- height: 100%;
- z-index: 1;
+ align-self: center;
+ width: auto;
+ height: 100%;
+ z-index: 1;
}
.map.constrainedHeight {
- width: auto;
- height: 100%;
+ width: auto;
+ height: 100%;
}
.mapCanvas {
- max-width: 100%;
- max-height: 100%;
+ max-width: 100%;
+ max-height: 100%;
}
.dropdown em {
- color: #6a6a6a;
- margin-right: 1rem;
+ color: #6a6a6a;
+ margin-right: 1rem;
}
.mobile {
- padding: var(--btn-padding);
- /* font-size: 30px; */
- flex: auto;
+ padding: var(--btn-padding);
+ /* font-size: 30px; */
+ flex: auto;
}
.mobile-map-save-btn {
- width: 54%;
- border-radius: 13px;
- border: 5px solid whitesmoke;
- padding: 10px;
- margin: 0.5rem;
- vertical-align: middle;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
+ width: 54%;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ padding: 10px;
+ margin: 0.5rem;
+ vertical-align: middle;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
}
.map-save-btn {
- width: 100%;
- border-radius: 13px;
- padding: 8px;
- vertical-align: middle;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-bottom: 0.5rem;
- text-align: center;
+ width: 100%;
+ border-radius: 13px;
+ padding: 8px;
+ vertical-align: middle;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 0.5rem;
+ text-align: center;
}
.mobile-map-play-btn {
- width: 32%;
- border-radius: 13px;
- border: 5px solid whitesmoke;
- padding: 10px;
- margin: 0.5rem;
- text-align: center;
- background: #06c7e1;
- vertical-align: middle;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
+ width: 32%;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ padding: 10px;
+ margin: 0.5rem;
+ text-align: center;
+ background: #06c7e1;
+ vertical-align: middle;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
}
.map-play-btn {
- width: 100%;
- border-radius: 13px;
- padding: 8px;
- text-align: center;
- background: var(--selected-color);
- vertical-align: middle;
- display: flex;
- align-items: center;
- margin-top: 0.5rem;
- justify-content: center;
- margin-bottom: 0.5rem;
+ width: 100%;
+ border-radius: 13px;
+ padding: 8px;
+ text-align: center;
+ background: var(--selected-color);
+ vertical-align: middle;
+ display: flex;
+ align-items: center;
+ margin-top: 0.5rem;
+ justify-content: center;
+ margin-bottom: 0.5rem;
}
.mobile-map-cancel-btn {
- width: 32%;
- border-radius: 13px;
- border: 5px solid whitesmoke;
- padding: 10px;
- margin: 0.5rem;
- text-align: center;
- background: #cd0b0b;
- vertical-align: middle;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- color: white;
+ width: 32%;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ padding: 10px;
+ margin: 0.5rem;
+ text-align: center;
+ background: #cd0b0b;
+ vertical-align: middle;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ color: white;
}
.map-cancel-btn {
- width: 100%;
- border-radius: 13px;
- padding: 8px;
- text-align: center;
- vertical-align: middle;
- display: flex;
- align-items: center;
- margin-top: 0.5rem;
- justify-content: center;
- margin-bottom: 0.5rem;
- background: #cd0b0b;
- display: flex;
- color: white;
+ width: 100%;
+ border-radius: 13px;
+ padding: 8px;
+ text-align: center;
+ vertical-align: middle;
+ display: flex;
+ align-items: center;
+ margin-top: 0.5rem;
+ justify-content: center;
+ margin-bottom: 0.5rem;
+ background: #cd0b0b;
+ display: flex;
+ color: white;
}
.map-fn-btns {
- display: grid;
- font-size: 18px;
- margin-top: 1rem;
- margin-bottom: 0.5rem;
- width: 100%;
+ display: grid;
+ font-size: 18px;
+ margin-top: 1rem;
+ margin-bottom: 0.5rem;
+ width: 100%;
}
.map-fn-btns-mobile {
- display: flex;
- /* font-size: 45px; */
+ display: flex;
+ /* font-size: 45px; */
}
diff --git a/src/pages/operator/css/MobileOperator.css b/src/pages/operator/css/MobileOperator.css
index 0cd8efe8..0b288691 100644
--- a/src/pages/operator/css/MobileOperator.css
+++ b/src/pages/operator/css/MobileOperator.css
@@ -1,224 +1,224 @@
#mobile-operator {
- height: 100%;
- touch-action: none;
- /* display: grid; */
- grid-template-rows: auto auto 1fr;
- grid-template-columns: 1fr auto;
- background-color: #7f7f7f;
+ height: 100%;
+ touch-action: none;
+ /* display: grid; */
+ grid-template-rows: auto auto 1fr;
+ grid-template-columns: 1fr auto;
+ background-color: #7f7f7f;
}
.switch-camera {
- z-index: 5;
- position: absolute;
- border-radius: 0px;
- opacity: 0.5;
- font-size: 2rem;
- padding: 0;
- float: right;
- text-align: right;
- margin-left: 85%;
- color: black;
+ z-index: 5;
+ position: absolute;
+ border-radius: 0px;
+ opacity: 0.5;
+ font-size: 2rem;
+ padding: 0;
+ float: right;
+ text-align: right;
+ margin-left: 85%;
+ color: black;
}
.switch-camera .material-icons {
- color: black;
+ color: black;
}
.record {
- z-index: 5;
- position: absolute;
- border-radius: 0px;
- opacity: 0.65;
- /* font-size: 50px; */
- text-align: right;
- display: flex;
- padding: 0.5rem;
- align-items: center;
- color: black;
+ z-index: 5;
+ position: absolute;
+ border-radius: 0px;
+ opacity: 0.65;
+ /* font-size: 50px; */
+ text-align: right;
+ display: flex;
+ padding: 0.5rem;
+ align-items: center;
+ color: black;
}
.depth-sensing {
- float: right;
- z-index: 5;
- position: absolute;
- border-radius: 0px;
- opacity: 0.5;
- /* font-size: 50px; */
- text-align: right;
- display: flex;
- align-items: center;
- /* width: 20rem; */
- /* margin-top: 85%; */
- /* margin-left: 68%; */
- background: whitesmoke;
+ float: right;
+ z-index: 5;
+ position: absolute;
+ border-radius: 0px;
+ opacity: 0.5;
+ /* font-size: 50px; */
+ text-align: right;
+ display: flex;
+ align-items: center;
+ /* width: 20rem; */
+ /* margin-top: 85%; */
+ /* margin-left: 68%; */
+ background: whitesmoke;
}
.pill {
- border-radius: 1.5rem;
+ border-radius: 1.5rem;
}
.active-color {
- color: white;
- background-color: #084298;
+ color: white;
+ background-color: #084298;
}
#mobile-operator-body {
- display: flex;
- flex-direction: column;
- height: 100%;
- /* flex: 1 1 0;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ /* flex: 1 1 0;
grid-column: 1/1; */
- /* grid-row: 3; */
+ /* grid-row: 3; */
}
.mobile-alert {
- position: absolute;
- width: 100%;
- z-index: 6;
+ position: absolute;
+ width: 100%;
+ z-index: 6;
}
/** https://stackoverflow.com/a/40989121 **/
.loader {
- position: absolute;
- top: calc(50% - 5em);
- left: calc(50% - 5em);
- width: 11em;
- height: 11em;
- border: 1.1em solid rgba(0, 0, 0, 0.2);
- border-left: 1.1em solid #000000;
- border-radius: 50%;
- animation: load 1s infinite linear;
+ position: absolute;
+ top: calc(50% - 5em);
+ left: calc(50% - 5em);
+ width: 11em;
+ height: 11em;
+ border: 1.1em solid rgba(0, 0, 0, 0.2);
+ border-left: 1.1em solid #000000;
+ border-radius: 50%;
+ animation: load 1s infinite linear;
}
.loading-text {
- position: absolute;
- top: calc(50% - 1em);
- left: calc(50% - 2em);
- font-size: large;
- z-index: 4;
+ position: absolute;
+ top: calc(50% - 1em);
+ left: calc(50% - 2em);
+ font-size: large;
+ z-index: 4;
}
.control-modes {
- touch-action: none;
- font-size: 3.5rem;
- justify-content: center;
- display: flex;
- padding-top: 20px;
- gap: 15px;
- padding-bottom: 50px;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ touch-action: none;
+ font-size: 3.5rem;
+ justify-content: center;
+ display: flex;
+ padding-top: 20px;
+ gap: 15px;
+ padding-bottom: 50px;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@keyframes load {
- 0% {
- transform: rotate(0deg);
- }
+ 0% {
+ transform: rotate(0deg);
+ }
- 100% {
- transform: rotate(360deg);
- }
+ 100% {
+ transform: rotate(360deg);
+ }
}
@media (max-width: 900px) {
- #operator-header .dropdown {
- width: var(--header-btn-width-med);
- }
+ #operator-header .dropdown {
+ width: var(--header-btn-width-med);
+ }
- #operator-header > *,
- #operator-header button {
- height: 3rem;
- }
+ #operator-header > *,
+ #operator-header button {
+ height: 3rem;
+ }
- .operator-voice,
- .operator-pose-library,
- .operator-pose-recorder,
- .operator-aruco-markers {
- width: 30rem;
- height: 5rem;
- }
+ .operator-voice,
+ .operator-pose-library,
+ .operator-pose-recorder,
+ .operator-aruco-markers {
+ width: 30rem;
+ height: 5rem;
+ }
}
.slider {
- height: 15px;
- touch-action: none;
- pointer-events: all;
+ height: 15px;
+ touch-action: none;
+ pointer-events: all;
}
.slider-container {
- display: flex;
- align-items: baseline;
- justify-content: space-around;
- padding-top: 16px;
+ display: flex;
+ align-items: baseline;
+ justify-content: space-around;
+ padding-top: 16px;
}
.slider {
- -webkit-appearance: none;
- width: 70%;
- /* height: 30px; */
- border-radius: 19px;
- background: #d3d3d3;
- outline: none;
- opacity: 0.7;
- -webkit-transition: 0.2s;
- transition: opacity 0.2s;
+ -webkit-appearance: none;
+ width: 70%;
+ /* height: 30px; */
+ border-radius: 19px;
+ background: #d3d3d3;
+ outline: none;
+ opacity: 0.7;
+ -webkit-transition: 0.2s;
+ transition: opacity 0.2s;
}
.slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 25px;
- height: 25px;
- border-radius: 50%;
- background: #4caf50;
- cursor: pointer;
+ -webkit-appearance: none;
+ appearance: none;
+ width: 25px;
+ height: 25px;
+ border-radius: 50%;
+ background: #4caf50;
+ cursor: pointer;
}
.label {
- font-size: 20px;
- padding-bottom: 10px;
+ font-size: 20px;
+ padding-bottom: 10px;
}
.map,
.controls {
- display: contents;
+ display: contents;
}
.map.hideMap,
.controls.hideControls {
- display: none;
+ display: none;
}
.record-circle {
- width: 20px;
- height: 20px;
- background-color: #bd1919;
- border-radius: 50%;
- margin: 0.5rem;
+ width: 20px;
+ height: 20px;
+ background-color: #bd1919;
+ border-radius: 50%;
+ margin: 0.5rem;
}
/** https://codepen.io/vram1980/pen/oNvWdO */
.recording {
- border: 3px solid #bd1919;
- -webkit-border-radius: 30px;
- height: 30px;
- width: 30px;
- position: absolute;
- left: 11px;
- top: 11px;
- -webkit-animation: pulsate 1s ease-out;
- -webkit-animation-iteration-count: infinite;
- opacity: 0;
+ border: 3px solid #bd1919;
+ -webkit-border-radius: 30px;
+ height: 30px;
+ width: 30px;
+ position: absolute;
+ left: 11px;
+ top: 11px;
+ -webkit-animation: pulsate 1s ease-out;
+ -webkit-animation-iteration-count: infinite;
+ opacity: 0;
}
@-webkit-keyframes pulsate {
- 0% {
- -webkit-transform: scale(0.1, 0.1);
- opacity: 0;
- }
- 50% {
- opacity: 1;
- }
- 100% {
- -webkit-transform: scale(1.2, 1.2);
- opacity: 0;
- }
+ 0% {
+ -webkit-transform: scale(0.1, 0.1);
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ -webkit-transform: scale(1.2, 1.2);
+ opacity: 0;
+ }
}
diff --git a/src/pages/operator/css/MovementRecorder.css b/src/pages/operator/css/MovementRecorder.css
index 4073246a..0d9d8cdc 100644
--- a/src/pages/operator/css/MovementRecorder.css
+++ b/src/pages/operator/css/MovementRecorder.css
@@ -1,8 +1,8 @@
#movement-recorder-container {
- display: flex;
- gap: 15px;
- align-items: center;
- justify-content: center;
+ display: flex;
+ gap: 15px;
+ align-items: center;
+ justify-content: center;
}
/* The below buttons' CSS likely gets overridden by the TextToSpeech CSS
@@ -11,49 +11,49 @@
* one of the components.
*/
.play-btn {
- background-color: var(--selected-color);
- display: flex;
+ background-color: var(--selected-color);
+ display: flex;
}
.save-btn {
- background-color: var(--btn-turquoise);
- display: flex;
+ background-color: var(--btn-turquoise);
+ display: flex;
}
.delete-btn {
- background-color: var(--btn-red);
- display: flex;
+ background-color: var(--btn-red);
+ display: flex;
}
@media (max-width: 1300px) {
- #movement-recorder-container {
- font-size: smaller;
- }
+ #movement-recorder-container {
+ font-size: smaller;
+ }
}
.mobile-movement-save-btn {
- border-radius: 13px;
- border: 5px solid whitesmoke;
- font-size: 25px;
- padding: 10px;
- margin: 1rem;
- text-align: center;
- background: #06c7e1;
- vertical-align: middle;
- display: flex;
- justify-content: center;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ font-size: 25px;
+ padding: 10px;
+ margin: 1rem;
+ text-align: center;
+ background: #06c7e1;
+ vertical-align: middle;
+ display: flex;
+ justify-content: center;
}
.mobile-movement-play-btn {
- /* width: 97%; */
- border-radius: 13px;
- border: 5px solid whitesmoke;
- font-size: 20px;
- padding: 10px;
- margin: 0.5rem;
- text-align: center;
- background: #06c7e1;
- vertical-align: middle;
- display: flex;
- justify-content: center;
+ /* width: 97%; */
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ font-size: 20px;
+ padding: 10px;
+ margin: 0.5rem;
+ text-align: center;
+ background: #06c7e1;
+ vertical-align: middle;
+ display: flex;
+ justify-content: center;
}
diff --git a/src/pages/operator/css/Operator.css b/src/pages/operator/css/Operator.css
index 3222a7e0..0b7a550f 100644
--- a/src/pages/operator/css/Operator.css
+++ b/src/pages/operator/css/Operator.css
@@ -1,79 +1,79 @@
#operator {
- height: 100%;
- display: grid;
- grid-template-rows: auto auto 1fr;
- grid-template-columns: 1fr auto;
+ height: 100%;
+ display: grid;
+ grid-template-rows: auto auto 1fr;
+ grid-template-columns: 1fr auto;
- --screen-padding: 1rem;
+ --screen-padding: 1rem;
}
#operator-header {
- background-color: var(--gray-bg);
- width: 100%;
- box-sizing: border-box;
- padding: var(--screen-padding);
- display: flex;
- align-items: center;
- justify-content: space-between;
- grid-row: 1;
- grid-column: 1/3;
- --header-btn-width: 11rem;
- --header-btn-width-med: 7rem;
+ background-color: var(--gray-bg);
+ width: 100%;
+ box-sizing: border-box;
+ padding: var(--screen-padding);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ grid-row: 1;
+ grid-column: 1/3;
+ --header-btn-width: 11rem;
+ --header-btn-width-med: 7rem;
}
.operator-collision-alerts {
- width: 100%;
- /* padding: 0.5rem 1rem 0 1rem; */
- position: absolute;
+ width: 100%;
+ /* padding: 0.5rem 1rem 0 1rem; */
+ position: absolute;
}
.operator-alert {
- font-size: 1.5rem;
+ font-size: 1.5rem;
}
.operator-collision-alerts .operator-alert {
- animation: fade-out 1s;
+ animation: fade-out 1s;
}
.operator-collision-alerts .operator-alert.fadeOut {
- display: none;
- animation: fade-out 2s;
- opacity: 0;
+ display: none;
+ animation: fade-out 2s;
+ opacity: 0;
}
.operator-collision-alerts .operator-alert.fadeIn {
- display: block;
- animation: fade-in 0.5s;
+ display: block;
+ animation: fade-in 0.5s;
}
@keyframes fade-in {
- from {
- opacity: 0;
- }
+ from {
+ opacity: 0;
+ }
- to {
- opacity: 1;
- }
+ to {
+ opacity: 1;
+ }
}
@keyframes fade-out {
- from {
- opacity: 1;
- }
+ from {
+ opacity: 1;
+ }
- to {
- opacity: 0;
- }
+ to {
+ opacity: 0;
+ }
}
/* Make all the components in the header fill the available height */
#operator-header > *,
#operator-header button {
- height: 100%;
+ height: 100%;
}
#operator-header .dropdown {
- width: var(--header-btn-width);
+ width: var(--header-btn-width);
}
.operator-voice,
@@ -81,34 +81,34 @@
.operator-pose-recorder,
.operator-text-to-speech,
.operator-aruco-markers {
- background-color: whitesmoke;
- box-shadow: var(--shadow);
- height: 6rem;
- width: 50rem;
- display: inline-grid;
- align-items: center;
- justify-content: center;
- /* justify-self: center; */
- align-self: center;
- border-radius: 200px;
- /* margin-top: 10px; */
- /* grid-row: 2/2;
+ background-color: whitesmoke;
+ box-shadow: var(--shadow);
+ height: 6rem;
+ width: 50rem;
+ display: inline-grid;
+ align-items: center;
+ justify-content: center;
+ /* justify-self: center; */
+ align-self: center;
+ border-radius: 200px;
+ /* margin-top: 10px; */
+ /* grid-row: 2/2;
grid-column: 1/1; */
- transition: all 0.2s ease-out;
- font-size: large;
+ transition: all 0.2s ease-out;
+ font-size: large;
}
.operator-pose-library.hideLabels,
.operator-pose-recorder.hideLabels {
- width: 32rem;
+ width: 32rem;
}
.operator-aruco-markers.hideLabels {
- width: 43rem;
+ width: 43rem;
}
.operator-aruco-markers {
- width: 66rem;
+ width: 66rem;
}
.operator-voice[hidden],
@@ -116,82 +116,82 @@
.operator-pose-recorder[hidden],
.operator-text-to-speech[hidden],
.operator-aruco-markers[hidden] {
- display: none;
+ display: none;
}
#operator-global-controls {
- display: flex;
- flex-wrap: wrap;
- row-gap: 10px;
- column-gap: 10px;
- justify-content: center;
+ display: flex;
+ flex-wrap: wrap;
+ row-gap: 10px;
+ column-gap: 10px;
+ justify-content: center;
}
#operator-body {
- display: flex;
- justify-content: center;
- flex-flow: row;
- flex: 1 1 0;
- grid-column: 1/1;
- grid-row: 3;
+ display: flex;
+ justify-content: center;
+ flex-flow: row;
+ flex: 1 1 0;
+ grid-column: 1/1;
+ grid-row: 3;
}
/** https://stackoverflow.com/a/40989121 **/
.loader {
- position: absolute;
- top: calc(50% - 5em);
- left: calc(50% - 5em);
- width: 11em;
- height: 11em;
- border: 1.1em solid rgba(0, 0, 0, 0.2);
- border-left: 1.1em solid #000000;
- border-radius: 50%;
- animation: load 1s infinite linear;
- background-color: white;
- z-index: 4;
+ position: absolute;
+ top: calc(50% - 5em);
+ left: calc(50% - 5em);
+ width: 11em;
+ height: 11em;
+ border: 1.1em solid rgba(0, 0, 0, 0.2);
+ border-left: 1.1em solid #000000;
+ border-radius: 50%;
+ animation: load 1s infinite linear;
+ background-color: white;
+ z-index: 4;
}
.loading-text {
- position: absolute;
- top: calc(50% - 1em);
- left: calc(50% - 2em);
- font-size: large;
- z-index: 4;
+ position: absolute;
+ top: calc(50% - 1em);
+ left: calc(50% - 2em);
+ font-size: large;
+ z-index: 4;
}
.reconnecting-text {
- position: absolute;
- top: calc(50% - 1em);
- left: calc(50% - 3em);
- font-size: large;
- z-index: 5;
+ position: absolute;
+ top: calc(50% - 1em);
+ left: calc(50% - 3em);
+ font-size: large;
+ z-index: 5;
}
@keyframes load {
- 0% {
- transform: rotate(0deg);
- }
+ 0% {
+ transform: rotate(0deg);
+ }
- 100% {
- transform: rotate(360deg);
- }
+ 100% {
+ transform: rotate(360deg);
+ }
}
@media (max-width: 900px) {
- #operator-header .dropdown {
- width: var(--header-btn-width-med);
- }
-
- #operator-header > *,
- #operator-header button {
- height: 3rem;
- }
-
- .operator-voice,
- .operator-pose-library,
- .operator-pose-recorder,
- .operator-aruco-markers {
- width: 30rem;
- height: 5rem;
- }
+ #operator-header .dropdown {
+ width: var(--header-btn-width-med);
+ }
+
+ #operator-header > *,
+ #operator-header button {
+ height: 3rem;
+ }
+
+ .operator-voice,
+ .operator-pose-library,
+ .operator-pose-recorder,
+ .operator-aruco-markers {
+ width: 30rem;
+ height: 5rem;
+ }
}
diff --git a/src/pages/operator/css/Panel.css b/src/pages/operator/css/Panel.css
index 531af9c4..ca9c6030 100644
--- a/src/pages/operator/css/Panel.css
+++ b/src/pages/operator/css/Panel.css
@@ -1,83 +1,83 @@
.tabs-component {
- height: 100%;
- display: flex;
- flex-direction: column;
- flex: 1 1 0;
- /* margin: 0 0.5rem; */
- margin-top: 0.5rem; /* new */
- width: 100%; /* new */
- box-shadow: var(--shadow);
- position: relative;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 0;
+ /* margin: 0 0.5rem; */
+ margin-top: 0.5rem; /* new */
+ width: 100%; /* new */
+ box-shadow: var(--shadow);
+ position: relative;
- --radius: 0.3rem;
- border-radius: var(--radius) var(--radius) 0px 0px;
+ --radius: 0.3rem;
+ border-radius: var(--radius) var(--radius) 0px 0px;
}
.tabs-component.customizing.selected::after {
- background-color: var(--selected-color);
+ background-color: var(--selected-color);
}
.tabs-content {
- border: 3px solid var(--btn-blue);
- flex: 1 1 0;
- background-color: var(--background-color);
+ border: 3px solid var(--btn-blue);
+ flex: 1 1 0;
+ background-color: var(--background-color);
}
/* Header *********************************************************************/
.tabs-header {
- display: flex;
- flex-wrap: wrap;
- overflow: hidden;
- border-radius: var(--radius) var(--radius) 0px 0px;
+ display: flex;
+ flex-wrap: wrap;
+ overflow: hidden;
+ border-radius: var(--radius) var(--radius) 0px 0px;
}
/* Apply to all the tabs in the header */
.tabs-header > * {
- flex: 1 0 auto;
- background-color: var(--tab-inactive);
- border-radius: var(--radius) var(--radius) 0px 0px;
+ flex: 1 0 auto;
+ background-color: var(--tab-inactive);
+ border-radius: var(--radius) var(--radius) 0px 0px;
}
/* Format tabs with icons */
.tabs-header > .material-icons {
- vertical-align: bottom;
- padding-top: 3px;
- padding-bottom: 3px;
+ vertical-align: bottom;
+ padding-top: 3px;
+ padding-bottom: 3px;
}
.tab-button {
- border: none;
- font-size: 1.5rem;
- z-index: 0;
+ border: none;
+ font-size: 1.5rem;
+ z-index: 0;
}
.tab-button.active {
- background-color: var(--btn-blue);
- color: var(--font-white);
- /* z-index: 2; */
- box-shadow: 0px 2px 2px 2px rgb(112 112 112 / 47%);
+ background-color: var(--btn-blue);
+ color: var(--font-white);
+ /* z-index: 2; */
+ box-shadow: 0px 2px 2px 2px rgb(112 112 112 / 47%);
}
.tab-button.selected {
- box-shadow: 0 0 0.2rem 0.2rem var(--selected-color);
+ box-shadow: 0 0 0.2rem 0.2rem var(--selected-color);
}
.add-tab:hover {
- background-color: var(--btn-turquoise);
- color: white;
+ background-color: var(--btn-turquoise);
+ color: white;
}
/* Popup for adding a new tab *************************************************/
@media (max-width: 900px) {
- .tab-button {
- font-size: medium;
- }
+ .tab-button {
+ font-size: medium;
+ }
}
@media screen and (orientation: portrait) and (max-device-width: 900px) {
- .tabs-component {
- width: 100%;
- }
+ .tabs-component {
+ width: 100%;
+ }
}
diff --git a/src/pages/operator/css/PoseLibrary.css b/src/pages/operator/css/PoseLibrary.css
index 7c9251d4..011439cd 100644
--- a/src/pages/operator/css/PoseLibrary.css
+++ b/src/pages/operator/css/PoseLibrary.css
@@ -1,27 +1,27 @@
#pose-library-container {
- display: flex;
- gap: 15px;
- align-items: center;
- justify-content: center;
+ display: flex;
+ gap: 15px;
+ align-items: center;
+ justify-content: center;
}
.play-btn {
- background-color: var(--selected-color);
- display: flex;
+ background-color: var(--selected-color);
+ display: flex;
}
.save-btn {
- background-color: var(--btn-turquoise);
- display: flex;
+ background-color: var(--btn-turquoise);
+ display: flex;
}
.delete-btn {
- background-color: var(--btn-red);
- display: flex;
+ background-color: var(--btn-red);
+ display: flex;
}
@media (max-width: 1300px) {
- #pose-library-container {
- font-size: smaller;
- }
+ #pose-library-container {
+ font-size: smaller;
+ }
}
diff --git a/src/pages/operator/css/PredictiveDisplay.css b/src/pages/operator/css/PredictiveDisplay.css
index 1fbd5735..54c745fc 100644
--- a/src/pages/operator/css/PredictiveDisplay.css
+++ b/src/pages/operator/css/PredictiveDisplay.css
@@ -1,21 +1,21 @@
.predictive-display {
- width: 100%;
- height: 100%;
- stroke: gray;
- cursor: crosshair;
- fill: none;
+ width: 100%;
+ height: 100%;
+ stroke: gray;
+ cursor: crosshair;
+ fill: none;
}
.predictive-display.customizing {
- cursor: default;
+ cursor: default;
}
.predictive-display path {
- stroke-width: 8;
- stroke: var(--path-blue);
- stroke-linecap: round;
+ stroke-width: 8;
+ stroke: var(--path-blue);
+ stroke-linecap: round;
}
.predictive-display.moving path {
- stroke: red;
+ stroke: red;
}
diff --git a/src/pages/operator/css/RadioGroup.css b/src/pages/operator/css/RadioGroup.css
index 14425df5..37bb132c 100644
--- a/src/pages/operator/css/RadioGroup.css
+++ b/src/pages/operator/css/RadioGroup.css
@@ -1,69 +1,69 @@
.radio-btn-mobile {
- font-size: 20px;
- border-radius: 13px;
- background-color: whitesmoke;
- margin: 0.5rem;
- border: 3px solid whitesmoke;
- filter: drop-shadow(6px 7px 2px rgb(0 0 0 / 0.4));
+ font-size: 20px;
+ border-radius: 13px;
+ background-color: whitesmoke;
+ margin: 0.5rem;
+ border: 3px solid whitesmoke;
+ filter: drop-shadow(6px 7px 2px rgb(0 0 0 / 0.4));
}
.radio-btn {
- font-size: 20px;
- border-radius: 4px;
- background-color: whitesmoke;
- padding: 5px;
- margin: 0.5rem;
- filter: drop-shadow(4px 2px 2px rgb(0 0 0 / 0.4));
+ font-size: 20px;
+ border-radius: 4px;
+ background-color: whitesmoke;
+ padding: 5px;
+ margin: 0.5rem;
+ filter: drop-shadow(4px 2px 2px rgb(0 0 0 / 0.4));
}
.radio {
- height: 1rem;
- width: 1rem;
- margin-right: 2rem;
+ height: 1rem;
+ width: 1rem;
+ margin-right: 2rem;
}
.radio-mobile {
- height: 1.25rem;
- width: 1.25rem;
- margin-right: 1.5rem;
+ height: 1.25rem;
+ width: 1.25rem;
+ margin-right: 1.5rem;
}
.radio-group {
- overflow-y: auto;
- width: 100%;
- max-height: 18vh;
+ overflow-y: auto;
+ width: 100%;
+ max-height: 18vh;
}
.radio-group-mobile {
- overflow-y: scroll;
+ overflow-y: scroll;
}
.modify {
- float: right;
- font-size: 25px;
+ float: right;
+ font-size: 25px;
}
.radio-icon {
- padding-right: 2rem;
+ padding-right: 2rem;
}
.add-btn {
- width: 97%;
- border-radius: 13px;
- border: 5px solid whitesmoke;
- font-size: 45px;
- padding: 20px;
- margin: 1rem;
- text-align: center;
+ width: 97%;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ font-size: 45px;
+ padding: 20px;
+ margin: 1rem;
+ text-align: center;
}
.start-btn {
- width: 97%;
- border-radius: 13px;
- border: 5px solid whitesmoke;
- font-size: 45px;
- padding: 20px;
- margin: 1rem;
- text-align: center;
- background: #06c7e1;
+ width: 97%;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ font-size: 45px;
+ padding: 20px;
+ margin: 1rem;
+ text-align: center;
+ background: #06c7e1;
}
diff --git a/src/pages/operator/css/RunStopButton.css b/src/pages/operator/css/RunStopButton.css
index 38ffd19f..de3aea0d 100644
--- a/src/pages/operator/css/RunStopButton.css
+++ b/src/pages/operator/css/RunStopButton.css
@@ -1,23 +1,23 @@
.run-stop-button {
- max-width: 110px;
- width: 100%;
+ max-width: 110px;
+ width: 100%;
}
.enabled {
- animation: blinker 1s linear infinite;
- filter: brightness(30%) saturate(100%) invert(10%) sepia(28%) saturate(4155%)
- hue-rotate(0deg) contrast(119%);
+ animation: blinker 1s linear infinite;
+ filter: brightness(30%) saturate(100%) invert(10%) sepia(28%)
+ saturate(4155%) hue-rotate(0deg) contrast(119%);
}
@keyframes blinker {
- 50% {
- opacity: 0;
- }
+ 50% {
+ opacity: 0;
+ }
}
.runStopContainer {
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
}
diff --git a/src/pages/operator/css/Sidebar.css b/src/pages/operator/css/Sidebar.css
index cf3437fc..bdde80f4 100644
--- a/src/pages/operator/css/Sidebar.css
+++ b/src/pages/operator/css/Sidebar.css
@@ -1,196 +1,196 @@
#sidebar {
- width: 20rem;
- grid-row: 2/4;
- grid-column: 2;
- height: 100%;
- background-color: var(--gray-bg);
- display: grid;
- grid-template-rows: 5rem auto 7rem;
- transition: width 0.5s ease-out;
- /* Darken the gray background color from index.css */
- --header-bg: color-mix(in srgb, var(--gray-bg) 90%, black);
- white-space: nowrap;
+ width: 20rem;
+ grid-row: 2/4;
+ grid-column: 2;
+ height: 100%;
+ background-color: var(--gray-bg);
+ display: grid;
+ grid-template-rows: 5rem auto 7rem;
+ transition: width 0.5s ease-out;
+ /* Darken the gray background color from index.css */
+ --header-bg: color-mix(in srgb, var(--gray-bg) 90%, black);
+ white-space: nowrap;
}
#sidebar[hidden] {
- width: 0;
+ width: 0;
}
#sidebar p {
- margin-top: 0;
- margin-bottom: 1rem;
+ margin-top: 0;
+ margin-bottom: 1rem;
}
#sidebar-header {
- background-color: var(--header-bg);
- padding: 1rem;
- display: flex;
- align-items: center;
- font-size: 18px;
+ background-color: var(--header-bg);
+ padding: 1rem;
+ display: flex;
+ align-items: center;
+ font-size: 18px;
}
#sidebar-body {
- padding: 1rem;
- box-shadow: inset 0 0 3px 0px var(--shadow-color);
+ padding: 1rem;
+ box-shadow: inset 0 0 3px 0px var(--shadow-color);
- display: flex;
- flex-direction: column;
- justify-content: space-between;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
}
/* Footer *********************************************************************/
#sidebar-footer {
- background-color: var(--header-bg);
+ background-color: var(--header-bg);
}
#delete-button {
- font-size: 3rem;
- padding: 1rem;
- /* Center it */
- display: block;
- width: 90%;
- margin: 0.8rem auto;
+ font-size: 3rem;
+ padding: 1rem;
+ /* Center it */
+ display: block;
+ width: 90%;
+ margin: 0.8rem auto;
}
/* Options ********************************************************************/
#sidebar-options > button {
- height: 5rem;
- width: 100%;
+ height: 5rem;
+ width: 100%;
}
.toggle-button-div {
- padding-bottom: 10px;
+ padding-bottom: 10px;
}
.toggle-button {
- display: inline-block;
- margin-right: 1rem;
- width: 7rem;
- height: 5rem;
- border-radius: var(--btn-brdr-radius);
+ display: inline-block;
+ margin-right: 1rem;
+ width: 7rem;
+ height: 5rem;
+ border-radius: var(--btn-brdr-radius);
}
.toggle-button.on {
- background-color: var(--btn-lightgreen);
+ background-color: var(--btn-lightgreen);
}
/* Component Provider *********************************************************/
#sidebar-component-provider {
- margin-bottom: 2rem;
- flex: 1;
- display: grid;
- grid-template-rows: auto 1fr;
+ margin-bottom: 2rem;
+ flex: 1;
+ display: grid;
+ grid-template-rows: auto 1fr;
}
#components-set {
- overflow-y: scroll;
- height: 100%;
+ overflow-y: scroll;
+ height: 100%;
}
.provider-tab {
- border-radius: var(--btn-brdr-radius);
- box-shadow: var(--shadow);
- margin-right: 0.5rem;
+ border-radius: var(--btn-brdr-radius);
+ box-shadow: var(--shadow);
+ margin-right: 0.5rem;
}
#sidebar-component-provider button {
- width: 100%;
- text-align: left;
- padding: var(--btn-padding);
- display: flex;
- align-items: center;
- height: 3rem;
- box-shadow: none;
+ width: 100%;
+ text-align: left;
+ padding: var(--btn-padding);
+ display: flex;
+ align-items: center;
+ height: 3rem;
+ box-shadow: none;
}
.provider-tab .active {
- background-color: var(--selected-color);
+ background-color: var(--selected-color);
}
.provider-tab button span {
- width: 2rem;
- transition: transform 0.2s linear;
+ width: 2rem;
+ transition: transform 0.2s linear;
}
.provider-tab button.expanded {
- filter: brightness(95%);
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
+ filter: brightness(95%);
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
}
.provider-tab button.expanded span {
- transform: scaleY(-1);
+ transform: scaleY(-1);
}
.provider-tab button.id-button {
- padding-left: 3rem;
+ padding-left: 3rem;
}
.id-button {
- transition: height 0.2s ease-out;
- border-radius: 0;
+ transition: height 0.2s ease-out;
+ border-radius: 0;
}
.id-button:last-of-type {
- border-bottom-left-radius: var(--btn-brdr-radius);
- border-bottom-right-radius: var(--btn-brdr-radius);
+ border-bottom-left-radius: var(--btn-brdr-radius);
+ border-bottom-right-radius: var(--btn-brdr-radius);
}
/* Global settings area *******************************************************/
#global-settings > button {
- display: block;
+ display: block;
}
#global-settings button {
- height: 3rem;
+ height: 3rem;
}
#global-settings > * {
- width: 100%;
+ width: 100%;
}
#global-settings > *,
.provider-tab {
- margin-bottom: 0.4rem;
+ margin-bottom: 0.4rem;
}
#load-layout-modal .dropdown p {
- margin: 0;
+ margin: 0;
}
#load-layout-modal .dropdown em {
- color: #6a6a6a;
- margin-right: 1rem;
+ color: #6a6a6a;
+ margin-right: 1rem;
}
#load-layout-modal .dropdown {
- width: 90%;
+ width: 90%;
}
.select-selected {
- background-color: dodgerblue;
+ background-color: dodgerblue;
}
.global-label {
- display: inline-flex;
- inline-size: 164px;
- text-wrap: wrap;
+ display: inline-flex;
+ inline-size: 164px;
+ text-wrap: wrap;
}
@media (max-width: 900px) {
- #sidebar {
- grid-template-rows: auto;
- }
-
- #delete-button {
- font-size: x-large;
- padding: 0.1rem;
- }
-
- #sidebar-body {
- display: block;
- overflow: scroll;
- }
+ #sidebar {
+ grid-template-rows: auto;
+ }
+
+ #delete-button {
+ font-size: x-large;
+ padding: 0.1rem;
+ }
+
+ #sidebar-body {
+ display: block;
+ overflow: scroll;
+ }
}
diff --git a/src/pages/operator/css/SimpleCameraView.css b/src/pages/operator/css/SimpleCameraView.css
index 951a748a..71de3809 100644
--- a/src/pages/operator/css/SimpleCameraView.css
+++ b/src/pages/operator/css/SimpleCameraView.css
@@ -1,46 +1,46 @@
.simple-video-container {
- position: relative;
- /* flex: 1 1 0; */
- /* display: grid; */
- justify-items: center;
- /* padding: 0.4rem; */
- /* height: 100%; */
- /* grid-template-columns: auto; */
- /* grid-template-rows: 1fr auto minmax(9rem, 1fr); */
- object-fit: cover;
- justify-content: center;
- /* align-items: center;
+ position: relative;
+ /* flex: 1 1 0; */
+ /* display: grid; */
+ justify-items: center;
+ /* padding: 0.4rem; */
+ /* height: 100%; */
+ /* grid-template-columns: auto; */
+ /* grid-template-rows: 1fr auto minmax(9rem, 1fr); */
+ object-fit: cover;
+ justify-content: center;
+ /* align-items: center;
flex-direction: column; */
- text-align: -webkit-center;
+ text-align: -webkit-center;
}
.simple-video-area {
- position: relative;
- grid-row: 2/2;
- width: 100%;
- max-height: 100%;
- max-width: 100%;
- object-fit: cover;
- display: inline-block;
- text-align: center;
+ position: relative;
+ grid-row: 2/2;
+ width: 100%;
+ max-height: 100%;
+ max-width: 100%;
+ object-fit: cover;
+ display: inline-block;
+ text-align: center;
}
.simple-video-canvas {
- display: inline-block;
- width: 100%;
- height: auto;
- object-fit: cover;
+ display: inline-block;
+ width: 100%;
+ height: auto;
+ object-fit: cover;
}
.simple-video-canvas.constrainedHeight {
- display: inline-block;
- /* width: auto;
+ display: inline-block;
+ /* width: auto;
height: 100%; */
- object-fit: cover;
+ object-fit: cover;
}
.simple-realsense {
- /* margin-top: -75%; */
+ /* margin-top: -75%; */
}
/* @media screen and (max-height: 800px) {
@@ -49,88 +49,88 @@
}
} */
.icon {
- border-radius: 60px;
- background-color: aliceblue;
- font-size: 100px;
- opacity: 0.5;
- color: black;
+ border-radius: 60px;
+ background-color: aliceblue;
+ font-size: 100px;
+ opacity: 0.5;
+ color: black;
}
.simple-overlay {
- font-size: 50px;
- position: absolute;
- z-index: 4;
- /* margin-top: 75%; */
- background-color: transparent;
- touch-action: none;
- box-shadow: none;
+ font-size: 50px;
+ position: absolute;
+ z-index: 4;
+ /* margin-top: 75%; */
+ background-color: transparent;
+ touch-action: none;
+ box-shadow: none;
}
.btn-left {
- width: 20%;
- height: 100%;
+ width: 20%;
+ height: 100%;
}
.btn-right {
- width: 20%;
- height: 100%;
- margin-left: 80%;
+ width: 20%;
+ height: 100%;
+ margin-left: 80%;
}
.btn-up {
- width: 100%;
+ width: 100%;
}
.btn-down {
- width: 100%;
- display: flex;
- justify-content: center;
+ width: 100%;
+ display: flex;
+ justify-content: center;
}
/* Context menu popup *********************************************************/
.video-context-menu {
- list-style-type: none;
- position: absolute;
- background-color: var(--background-color);
- /* border: var(--btn-brdr); */
- margin: 0;
- padding: 0;
- white-space: nowrap;
+ list-style-type: none;
+ position: absolute;
+ background-color: var(--background-color);
+ /* border: var(--btn-brdr); */
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
- --padding: 1rem;
+ --padding: 1rem;
- padding-top: var(--padding);
- grid-row: 2/2;
+ padding-top: var(--padding);
+ grid-row: 2/2;
}
.video-context-menu li {
- padding: var(--padding);
- cursor: pointer;
- background-color: inherit;
+ padding: var(--padding);
+ cursor: pointer;
+ background-color: inherit;
}
.video-context-menu li:hover {
- filter: brightness(90%);
+ filter: brightness(90%);
}
.video-context-menu::before {
- content: attr(aria-label);
- font-weight: bold;
- padding: var(--padding);
+ content: attr(aria-label);
+ font-weight: bold;
+ padding: var(--padding);
}
.title {
- font-size: x-large;
- align-self: flex-end;
- text-align: center;
+ font-size: x-large;
+ align-self: flex-end;
+ text-align: center;
}
@media screen and (orientation: landscape) and (max-device-width: 900px) {
- .realsense-pan-tilt-grid button .material-icons {
- font-size: larger;
- }
+ .realsense-pan-tilt-grid button .material-icons {
+ font-size: larger;
+ }
- .title {
- font-size: smaller;
- }
+ .title {
+ font-size: smaller;
+ }
}
diff --git a/src/pages/operator/css/SpeedControl.css b/src/pages/operator/css/SpeedControl.css
index 289f945e..5537fd56 100644
--- a/src/pages/operator/css/SpeedControl.css
+++ b/src/pages/operator/css/SpeedControl.css
@@ -1,19 +1,19 @@
#velocity-control-container button:first-of-type {
- border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
+ border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
}
#velocity-control-container button:last-of-type {
- border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
+ border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
}
#velocity-control-container button {
- border-radius: 0;
- /* Make buttons wider than default */
- width: 8rem;
+ border-radius: 0;
+ /* Make buttons wider than default */
+ width: 8rem;
}
@media (max-width: 1000px) {
- #velocity-control-container button {
- width: auto;
- }
+ #velocity-control-container button {
+ width: auto;
+ }
}
diff --git a/src/pages/operator/css/TabGroup.css b/src/pages/operator/css/TabGroup.css
index 7dcd07eb..a3bbf2ad 100644
--- a/src/pages/operator/css/TabGroup.css
+++ b/src/pages/operator/css/TabGroup.css
@@ -1,75 +1,75 @@
.tab {
- font-size: 20px;
- /* padding-top: 10px; */
- cursor: pointer;
- outline: 0;
- display: flex;
- border-bottom: 5px solid black;
- justify-content: space-evenly;
- background-color: transparent;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ font-size: 20px;
+ /* padding-top: 10px; */
+ cursor: pointer;
+ outline: 0;
+ display: flex;
+ border-bottom: 5px solid black;
+ justify-content: space-evenly;
+ background-color: transparent;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.pill-tab {
- /* font-size: 45px; */
- cursor: pointer;
- outline: 0;
- display: flex;
- justify-content: space-between;
- background-color: transparent;
- -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
- width: 80%;
- height: auto;
- background: #052a33;
- margin: 1rem auto 1rem;
- color: #e8f0f2;
- border-radius: 10rem;
- list-style: none;
+ /* font-size: 45px; */
+ cursor: pointer;
+ outline: 0;
+ display: flex;
+ justify-content: space-between;
+ background-color: transparent;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ width: 80%;
+ height: auto;
+ background: #052a33;
+ margin: 1rem auto 1rem;
+ color: #e8f0f2;
+ border-radius: 10rem;
+ list-style: none;
}
.tab-btn {
- background-color: transparent;
- border-bottom: none;
- color: white;
- font-weight: 500;
+ background-color: transparent;
+ border-bottom: none;
+ color: white;
+ font-weight: 500;
}
li.pill-tab-btn {
- background-color: transparent;
- border-bottom: none;
- padding: 0.5rem;
- padding-left: 2rem;
- padding-right: 2rem;
+ background-color: transparent;
+ border-bottom: none;
+ padding: 0.5rem;
+ padding-left: 2rem;
+ padding-right: 2rem;
}
li.pill-tab-btn:first-child {
- border-bottom-left-radius: 5rem;
- border-top-left-radius: 5rem;
+ border-bottom-left-radius: 5rem;
+ border-top-left-radius: 5rem;
}
li.pill-tab-btn:last-child {
- border-bottom-right-radius: 5rem;
- border-top-right-radius: 5rem;
+ border-bottom-right-radius: 5rem;
+ border-top-right-radius: 5rem;
}
.tab-btn.active {
- border-bottom: 5px solid #06c7e1;
+ border-bottom: 5px solid #06c7e1;
}
.pill-tab-btn.active {
- background: #39a2db;
+ background: #39a2db;
}
.tab-content {
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
- flex: 1 1 0;
- /* padding-bottom: 10px; */
- justify-content: space-between;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
+ flex: 1 1 0;
+ /* padding-bottom: 10px; */
+ justify-content: space-between;
}
.tab-group {
- display: contents;
+ display: contents;
}
diff --git a/src/pages/operator/css/TextToSpeech.css b/src/pages/operator/css/TextToSpeech.css
index f7bbbcae..f32da0f0 100644
--- a/src/pages/operator/css/TextToSpeech.css
+++ b/src/pages/operator/css/TextToSpeech.css
@@ -1,60 +1,60 @@
#text-to-speech-container {
- display: flex;
- gap: 15px;
- align-items: center;
- justify-content: center;
+ display: flex;
+ gap: 15px;
+ align-items: center;
+ justify-content: center;
}
.play-btn {
- background-color: var(--selected-color);
- display: flex;
+ background-color: var(--selected-color);
+ display: flex;
}
.save-btn {
- background-color: var(--btn-turquoise);
- display: flex;
+ background-color: var(--btn-turquoise);
+ display: flex;
}
.stop-btn {
- background-color: #cd0b0b;
- color: white;
- display: flex;
+ background-color: #cd0b0b;
+ color: white;
+ display: flex;
}
.delete-btn {
- background-color: var(--btn-red);
- display: flex;
+ background-color: var(--btn-red);
+ display: flex;
}
@media (max-width: 1300px) {
- #text-to-speech-container {
- font-size: smaller;
- }
+ #text-to-speech-container {
+ font-size: smaller;
+ }
}
.mobile-text-save-btn {
- border-radius: 13px;
- border: 5px solid whitesmoke;
- font-size: 25px;
- padding: 10px;
- margin: 1rem;
- text-align: center;
- background: #06c7e1;
- vertical-align: middle;
- display: flex;
- justify-content: center;
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ font-size: 25px;
+ padding: 10px;
+ margin: 1rem;
+ text-align: center;
+ background: #06c7e1;
+ vertical-align: middle;
+ display: flex;
+ justify-content: center;
}
.mobile-text-play-btn {
- /* width: 97%; */
- border-radius: 13px;
- border: 5px solid whitesmoke;
- font-size: 20px;
- padding: 10px;
- margin: 0.5rem;
- text-align: center;
- background: #06c7e1;
- vertical-align: middle;
- display: flex;
- justify-content: center;
+ /* width: 97%; */
+ border-radius: 13px;
+ border: 5px solid whitesmoke;
+ font-size: 20px;
+ padding: 10px;
+ margin: 0.5rem;
+ text-align: center;
+ background: #06c7e1;
+ vertical-align: middle;
+ display: flex;
+ justify-content: center;
}
diff --git a/src/pages/operator/css/Tooltip.css b/src/pages/operator/css/Tooltip.css
index 0c5323e5..c681f0f5 100644
--- a/src/pages/operator/css/Tooltip.css
+++ b/src/pages/operator/css/Tooltip.css
@@ -1,105 +1,105 @@
.tooltip-trigger .tooltip-top::after {
- content: " ";
- position: absolute;
- top: 100%;
- left: 50%;
- margin-left: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: black transparent transparent transparent;
+ content: " ";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: black transparent transparent transparent;
}
.tooltip-trigger .tooltip-bottom::after {
- content: " ";
- position: absolute;
- bottom: 100%;
- left: 50%;
- margin-left: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: transparent transparent black transparent;
+ content: " ";
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent black transparent;
}
.tooltip-trigger .tooltip {
- display: none;
- opacity: 0;
+ display: none;
+ opacity: 0;
}
.tooltip-trigger:hover .tooltip {
- display: block;
- opacity: 1;
- animation: fade-in 0.5s;
+ display: block;
+ opacity: 1;
+ animation: fade-in 0.5s;
}
@keyframes fade-in {
- from {
- opacity: 0;
- }
+ from {
+ opacity: 0;
+ }
- to {
- opacity: 1;
- }
+ to {
+ opacity: 1;
+ }
}
.tooltip-trigger {
- position: relative;
- display: inline;
+ position: relative;
+ display: inline;
}
.tooltip-trigger .tooltip {
- width: fit-content;
- min-width: 100%;
- left: 50%;
- transform: translateX(-50%);
- background-color: black;
- color: #fff;
- text-align: center;
- border-radius: 6px;
- padding: 5px;
- opacity: 0;
- transition: opacity 1s;
- position: absolute;
- z-index: 1;
+ width: fit-content;
+ min-width: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: black;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px;
+ opacity: 0;
+ transition: opacity 1s;
+ position: absolute;
+ z-index: 1;
}
.tooltip-right {
- top: -5px;
- left: 105%;
+ top: -5px;
+ left: 105%;
}
.tooltip-left {
- top: -5px;
- right: 105%;
+ top: -5px;
+ right: 105%;
}
.tooltip-top {
- bottom: 105%;
- left: 0%;
+ bottom: 105%;
+ left: 0%;
}
.tooltip-bottom {
- top: 105%;
- left: 0%;
+ top: 105%;
+ left: 0%;
}
.tooltip-trigger .tooltip-right::after {
- content: " ";
- position: absolute;
- top: 50%;
- right: 100%;
- margin-top: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: transparent black transparent transparent;
+ content: " ";
+ position: absolute;
+ top: 50%;
+ right: 100%;
+ margin-top: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent black transparent transparent;
}
.tooltip-trigger .tooltip-left::after {
- content: " ";
- position: absolute;
- top: 50%;
- left: 100%;
- margin-top: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: transparent transparent transparent black;
+ content: " ";
+ position: absolute;
+ top: 50%;
+ left: 100%;
+ margin-top: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent transparent black;
}
diff --git a/src/pages/operator/css/VirtualJoystick.css b/src/pages/operator/css/VirtualJoystick.css
index c1b3ffb4..fe430d90 100644
--- a/src/pages/operator/css/VirtualJoystick.css
+++ b/src/pages/operator/css/VirtualJoystick.css
@@ -1,44 +1,44 @@
.virtual-joystick {
- flex: 1;
- border-radius: var(--btn-brdr-radius);
- /* border: 2px solid var(--btn-blue); */
- margin: 0.4rem;
- position: relative;
- z-index: 1;
- max-height: 100%;
+ flex: 1;
+ border-radius: var(--btn-brdr-radius);
+ /* border: 2px solid var(--btn-blue); */
+ margin: 0.4rem;
+ position: relative;
+ z-index: 1;
+ max-height: 100%;
- display: grid;
- justify-items: center;
+ display: grid;
+ justify-items: center;
}
.virtual-joystick svg {
- max-height: 100%;
- max-width: fit-content;
+ max-height: 100%;
+ max-width: fit-content;
}
.virtual-joystick:not(.customizing) {
- cursor: crosshair;
+ cursor: crosshair;
}
.virtual-joystick::after {
- border-radius: var(--btn-brdr-radius);
+ border-radius: var(--btn-brdr-radius);
}
.virtual-joystick path {
- fill: none;
- stroke-width: 1;
- stroke: lightgray;
+ fill: none;
+ stroke-width: 1;
+ stroke: lightgray;
}
.virtual-joystick .joystick {
- fill: darkgray;
+ fill: darkgray;
}
.virtual-joystick .outer-circle {
- /* fill: lightgray; */
- fill: #312e2e;
+ /* fill: lightgray; */
+ fill: #312e2e;
}
.virtual-joystick.active .joystick {
- fill: var(--btn-turquoise);
+ fill: var(--btn-turquoise);
}
diff --git a/src/pages/operator/css/basic_components.css b/src/pages/operator/css/basic_components.css
index df24a91d..b706c500 100644
--- a/src/pages/operator/css/basic_components.css
+++ b/src/pages/operator/css/basic_components.css
@@ -1,380 +1,380 @@
/* Popup modal ****************************************************************/
.popup-modal {
- position: fixed;
- border-radius: var(--btn-brdr-radius);
- background-color: var(--background-color);
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- padding: 1rem;
- z-index: 10;
- height: 50%;
- width: 50%;
- display: flex;
- flex-direction: column;
- /* align-items: center; */
- justify-content: space-between;
- opacity: 100%;
- touch-action: manipulation;
+ position: fixed;
+ border-radius: var(--btn-brdr-radius);
+ background-color: var(--background-color);
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ padding: 1rem;
+ z-index: 10;
+ height: 50%;
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ /* align-items: center; */
+ justify-content: space-between;
+ opacity: 100%;
+ touch-action: manipulation;
}
.popup-modal.small {
- height: 15rem;
- width: 20rem;
- top: 10%;
+ height: 15rem;
+ width: 20rem;
+ top: 10%;
}
.popup-modal.medium {
- height: 50rem;
- width: 47rem;
- top: -10%;
- /* height: 35%; */
+ height: 50rem;
+ width: 47rem;
+ top: -10%;
+ /* height: 35%; */
}
.popup-modal.large {
- height: 50%;
+ height: 50%;
}
.mobile {
- transform: translate(-50%, 50%);
- display: flex;
- justify-content: space-around;
- /* font-size: 50px !important; */
+ transform: translate(-50%, 50%);
+ display: flex;
+ justify-content: space-around;
+ /* font-size: 50px !important; */
}
.voice-commands-popup-modal {
- position: fixed;
- border-radius: var(--btn-brdr-radius);
- background-color: var(--background-color);
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- padding: 2rem;
- z-index: 4;
- height: 45rem;
- width: 30rem;
- display: flex;
- flex-direction: column;
- /* align-items: center; */
- justify-content: space-between;
- opacity: 100%;
+ position: fixed;
+ border-radius: var(--btn-brdr-radius);
+ background-color: var(--background-color);
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ padding: 2rem;
+ z-index: 4;
+ height: 45rem;
+ width: 30rem;
+ display: flex;
+ flex-direction: column;
+ /* align-items: center; */
+ justify-content: space-between;
+ opacity: 100%;
}
.voice-commands-popup-modal #close-modal {
- padding-left: 80%;
+ padding-left: 80%;
}
.voice-commands-popup-modal #commands {
- padding-right: 20%;
+ padding-right: 20%;
}
.popup-modal-bottom-buttons {
- width: 100%;
+ width: 100%;
}
.popup-modal-bottom-buttons button {
- width: 45%;
- padding: 0.5em;
- color: black;
+ width: 45%;
+ padding: 0.5em;
+ color: black;
}
/* Dark background behind popup */
#popup-background,
.loader-background {
- position: fixed;
- top: 0;
- left: 0;
- height: 100%;
- width: 100%;
- background-color: black;
- opacity: 60%;
- z-index: 3;
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ background-color: black;
+ opacity: 60%;
+ z-index: 3;
}
.popup-modal label {
- font-size: 30px;
+ font-size: 30px;
}
.popup-modal input {
- width: 100%;
- padding: 0.5em;
+ width: 100%;
+ padding: 0.5em;
}
/* Dropdown *******************************************************************/
.dropdown {
- position: relative;
+ position: relative;
}
.dropdown-button,
.dropdown-input-button {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-top: 1rem;
- padding-bottom: 1rem;
- width: 100%;
- position: relative;
- color: black;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ width: 100%;
+ position: relative;
+ color: black;
}
.dropdown-button.expanded.bottom,
.dropdown-input-button.expanded.bottom {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- color: black;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ color: black;
}
.dropdown-button.expanded.top,
.dropdown-input-button.expanded.top {
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- margin: 0 !important;
- color: black;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ margin: 0 !important;
+ color: black;
}
/* Flip the dropdown arrow when active */
.dropdown-button span,
.dropdown-input-button span {
- transition: transform 0.2s linear;
+ transition: transform 0.2s linear;
}
.dropdown-button.expanded span,
.dropdown-input-button.expanded span {
- transform: scaleY(-1);
+ transform: scaleY(-1);
}
.dropdown-popup,
.dropdown-input-popup {
- position: absolute;
- min-width: 100%;
- z-index: 3;
- box-shadow: var(--shadow);
- border-radius: 0 0 var(--btn-brdr-radius) var(--btn-brdr-radius);
- top: 100%;
- bottom: auto;
- overflow-y: auto; /* Make it scrollable */
+ position: absolute;
+ min-width: 100%;
+ z-index: 3;
+ box-shadow: var(--shadow);
+ border-radius: 0 0 var(--btn-brdr-radius) var(--btn-brdr-radius);
+ top: 100%;
+ bottom: auto;
+ overflow-y: auto; /* Make it scrollable */
}
.dropdown-popup.top,
.dropdown-input-popup.top {
- top: auto;
- bottom: 100%;
- box-shadow: var(--shadow-bottom);
+ top: auto;
+ bottom: 100%;
+ box-shadow: var(--shadow-bottom);
}
.dropdown-option,
.dropdown-input-option {
- padding-top: 1rem;
- padding-bottom: 1rem;
- cursor: pointer;
- width: 100%;
- display: block;
- border-radius: 0;
- text-align: left;
- margin: 0 !important;
- color: black;
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ cursor: pointer;
+ width: 100%;
+ display: block;
+ border-radius: 0;
+ text-align: left;
+ margin: 0 !important;
+ color: black;
}
.dropdown-option.active,
.dropdown-input-option.active {
- filter: brightness(80%);
+ filter: brightness(80%);
}
.dropdown-popup.top .dropdown-option:first-of-type,
.dropdown-input-popup.top .dropdown-option:first-of-type {
- border-radius: var(--btn-brdr-radius) var(--btn-brdr-radius) 0 0;
+ border-radius: var(--btn-brdr-radius) var(--btn-brdr-radius) 0 0;
}
.dropdown-popup.top .dropdown-option:last-of-type,
.dropdown-input-popup.top .dropdown-option:last-of-type {
- box-shadow: none;
+ box-shadow: none;
}
.dropdown-popup.bottom .dropdown-option:last-of-type,
.dropdown-input-popup.bottom .dropdown-option:last-of-type {
- border-radius: 0 0 var(--btn-brdr-radius) var(--btn-brdr-radius);
+ border-radius: 0 0 var(--btn-brdr-radius) var(--btn-brdr-radius);
}
/* Dropdown Input **************************************************************/
.dropdown-input {
- position: relative;
- display: flex;
- align-items: stretch;
- justify-content: center;
+ position: relative;
+ display: flex;
+ align-items: stretch;
+ justify-content: center;
}
.dropdown-input:focus-within {
- border: 1px solid black;
- border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
+ border: 1px solid black;
+ border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
}
.dropdown-input-textarea {
- border-right-width: 0px !important;
- resize: none;
- border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
- box-shadow: var(--shadow);
- border: none;
- padding: 0.25rem 0 0.25rem 0.5rem;
+ border-right-width: 0px !important;
+ resize: none;
+ border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
+ box-shadow: var(--shadow);
+ border: none;
+ padding: 0.25rem 0 0.25rem 0.5rem;
}
.dropdown-input-textarea:focus {
- border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
- outline: none;
+ border-radius: var(--btn-brdr-radius) 0 0 var(--btn-brdr-radius);
+ outline: none;
}
.dropdown-input-button {
- width: auto !important;
- padding-top: 0rem;
- padding-bottom: 0rem;
- box-shadow: none;
- border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
- box-shadow: var(--shadow);
- /* border: 1px solid light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); */
- border-left-width: 0px !important;
+ width: auto !important;
+ padding-top: 0rem;
+ padding-bottom: 0rem;
+ box-shadow: none;
+ border-radius: 0 var(--btn-brdr-radius) var(--btn-brdr-radius) 0;
+ box-shadow: var(--shadow);
+ /* border: 1px solid light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); */
+ border-left-width: 0px !important;
}
/* CheckToggleButton **********************************************************/
.check-toggle-button {
- display: flex;
- justify-content: center;
- color: black;
+ display: flex;
+ justify-content: center;
+ color: black;
}
.check-toggle-button span.material-icons {
- margin-right: 1rem;
+ margin-right: 1rem;
}
.check-toggle-button,
.check-toggle-button-mobile {
- border-radius: 13px;
- display: flex;
- justify-content: center;
- align-items: center;
- text-align: center;
- background: transparent;
+ border-radius: 13px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ background: transparent;
}
.check-toggle-button-mobile {
- border: 5px solid whitesmoke;
- /* margin: 1rem; */
+ border: 5px solid whitesmoke;
+ /* margin: 1rem; */
}
.pose-name {
- /* padding-top: 5%; */
- align-self: normal;
+ /* padding-top: 5%; */
+ align-self: normal;
}
.pose-name label {
- padding-right: 2.5rem;
+ padding-right: 2.5rem;
}
.pose-name input {
- width: 65%;
+ width: 65%;
}
.mobile-pose-name input {
- width: 100%;
+ width: 100%;
}
ul.checkbox {
- margin: 0;
- padding: 0;
- margin-left: 1rem;
- list-style: none;
+ margin: 0;
+ padding: 0;
+ margin-left: 1rem;
+ list-style: none;
}
ul.checkbox li {
- border: 0.5rem transparent solid;
+ border: 0.5rem transparent solid;
}
hr {
- width: 100%;
+ width: 100%;
}
button .material-icons {
- margin: 0 !important;
+ margin: 0 !important;
}
input[type="checkbox"] {
- width: 1.5rem;
- height: 1.5rem;
- background: white;
- border-radius: 5px;
- border: 2px solid #555;
- margin-right: 1rem;
+ width: 1.5rem;
+ height: 1.5rem;
+ background: white;
+ border-radius: 5px;
+ border: 2px solid #555;
+ margin-right: 1rem;
}
@media screen and (max-device-width: 700px) {
- input[type="checkbox"] {
- margin-right: 3rem;
- transform: scale(2);
- }
+ input[type="checkbox"] {
+ margin-right: 3rem;
+ transform: scale(2);
+ }
- .popup-modal label {
- font-size: 50px;
- }
+ .popup-modal label {
+ font-size: 50px;
+ }
}
/* AccordionSelect **********************************************************/
/* Style the accordion section */
.accordion_section {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
- /* background-color: #eee; */
- /* color: #444; */
- cursor: pointer;
- /* padding: 18px; */
- display: flex;
- align-items: center;
- border: none;
- outline: none;
- transition: background-color 0.6s ease;
- justify-content: space-between;
+ /* background-color: #eee; */
+ /* color: #444; */
+ cursor: pointer;
+ /* padding: 18px; */
+ display: flex;
+ align-items: center;
+ border: none;
+ outline: none;
+ transition: background-color 0.6s ease;
+ justify-content: space-between;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.accordion:hover,
.active {
- background-image: linear-gradient(rgba(0, 0, 0, 0.05) 0 0);
+ background-image: linear-gradient(rgba(0, 0, 0, 0.05) 0 0);
}
/* Style the accordion content title */
.accordion_title {
- font-family: "Open Sans", sans-serif;
- font-weight: 600;
- font-size: 14px;
+ font-family: "Open Sans", sans-serif;
+ font-weight: 600;
+ font-size: 14px;
}
/* Style the accordion content panel. Note: hidden by default */
.accordion_content {
- background-color: var(--btn-gray);
- overflow: hidden;
- transition: max-height 0.6s ease;
+ background-color: var(--btn-gray);
+ overflow: hidden;
+ transition: max-height 0.6s ease;
}
/* Flip the dropdown arrow when active */
.accordion span {
- transition: transform 0.2s linear;
+ transition: transform 0.2s linear;
}
.accordion.active span {
- transform: scaleY(-1);
+ transform: scaleY(-1);
}
.accordion-item {
- padding: 0.75rem 0rem 0.75rem 0rem;
- text-align: center;
+ padding: 0.75rem 0rem 0.75rem 0rem;
+ text-align: center;
}
.accordion-item:hover {
- background-image: linear-gradient(rgba(0, 0, 0, 0.15) 0 0);
+ background-image: linear-gradient(rgba(0, 0, 0, 0.15) 0 0);
}
diff --git a/src/pages/operator/css/index.css b/src/pages/operator/css/index.css
index c5097051..1853f386 100644
--- a/src/pages/operator/css/index.css
+++ b/src/pages/operator/css/index.css
@@ -1,158 +1,158 @@
:root {
- --btn-brdr-radius: 0.4rem;
- --btn-padding: 0.5em 1em;
- --btn-padding-med: 0.4rem 0.5rem;
+ --btn-brdr-radius: 0.4rem;
+ --btn-padding: 0.5em 1em;
+ --btn-padding-med: 0.4rem 0.5rem;
- --selected-color: hsl(33, 95%, 63%);
+ --selected-color: hsl(33, 95%, 63%);
- --btn-gray: #f0f0f0;
- --btn-blue: hsl(200deg 83.23% 22.29%);
- --btn-green: rgb(129, 218, 129);
- --btn-red: hsl(0, 70%, 70%);
- --btn-turquoise: hsl(171, 32%, 46%);
+ --btn-gray: #f0f0f0;
+ --btn-blue: hsl(200deg 83.23% 22.29%);
+ --btn-green: rgb(129, 218, 129);
+ --btn-red: hsl(0, 70%, 70%);
+ --btn-turquoise: hsl(171, 32%, 46%);
- --shadow-color: rgba(112, 112, 112, 0.196);
+ --shadow-color: rgba(112, 112, 112, 0.196);
- --background-color: hsl(0, 0%, 98%);
- --gray-bg: hsl(0, 0%, 88%);
+ --background-color: hsl(0, 0%, 98%);
+ --gray-bg: hsl(0, 0%, 88%);
- --shadow: 3px 3px 2px var(--shadow-color);
+ --shadow: 3px 3px 2px var(--shadow-color);
- --tab-inactive: hsl(200, 20%, 85%);
+ --tab-inactive: hsl(200, 20%, 85%);
- --font-white: hsl(0, 0%, 100%);
+ --font-white: hsl(0, 0%, 100%);
- --path-blue: hsl(170, 37%, 53%);
+ --path-blue: hsl(170, 37%, 53%);
- --shadow-bottom: 3px 0px 0px var(--shadow-color);
+ --shadow-bottom: 3px 0px 0px var(--shadow-color);
}
* {
- box-sizing: border-box;
- min-width: 0;
- min-height: 0;
- font: inherit;
+ box-sizing: border-box;
+ min-width: 0;
+ min-height: 0;
+ font: inherit;
}
body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
- "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- background-color: var(--background-color);
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
+ "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ background-color: var(--background-color);
}
html {
- -webkit-touch-callout: none;
- -webkit-tap-highlight-color: transparent;
- -moz-user-select: -moz-none;
- -webkit-user-select: none;
- -ms-user-select: none;
- user-select: none;
+ -webkit-touch-callout: none;
+ -webkit-tap-highlight-color: transparent;
+ -moz-user-select: -moz-none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
html,
body,
#root {
- height: 100%;
- margin: 0;
- overflow: hidden;
- user-select: none;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ user-select: none;
}
@media screen and (orientation: landscape) and (max-device-width: 900px) {
- html,
- body,
- #root {
- overflow: scroll;
- }
+ html,
+ body,
+ #root {
+ overflow: scroll;
+ }
}
/* In customization mode add hidden shadow after each element, to be shown when
the element is selected */
.customizing::after {
- content: "";
- position: absolute;
+ content: "";
+ position: absolute;
- z-index: -1;
- width: 100%;
- height: 100%;
- opacity: 0;
- left: 0;
- top: 0;
+ z-index: -1;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ left: 0;
+ top: 0;
- box-shadow: 0 0 1rem 0.5rem var(--selected-color);
- transition: opacity 0.25s ease-in-out;
+ box-shadow: 0 0 1rem 0.5rem var(--selected-color);
+ transition: opacity 0.25s ease-in-out;
}
.customizing.selected::after {
- opacity: 1;
+ opacity: 1;
}
/* Button colors **************************************************************/
button {
- background-color: var(--btn-gray);
- border: none;
- border-radius: var(--btn-brdr-radius);
- padding: var(--btn-padding);
- box-shadow: var(--shadow);
- cursor: pointer;
+ background-color: var(--btn-gray);
+ border: none;
+ border-radius: var(--btn-brdr-radius);
+ padding: var(--btn-padding);
+ box-shadow: var(--shadow);
+ cursor: pointer;
}
button:hover {
- filter: brightness(90%);
- box-shadow: none;
+ filter: brightness(90%);
+ box-shadow: none;
}
button:disabled {
- background-color: lightgray;
- pointer-events: none;
- box-shadow: none;
+ background-color: lightgray;
+ pointer-events: none;
+ box-shadow: none;
}
button .material-icons {
- margin: -0.25rem;
+ margin: -0.25rem;
}
.btn-green {
- background-color: var(--btn-green);
+ background-color: var(--btn-green);
}
.btn-red {
- background-color: var(--btn-red);
+ background-color: var(--btn-red);
}
.btn-blue {
- background-color: var(--btn-blue);
+ background-color: var(--btn-blue);
}
.btn-turquoise {
- background-color: var(--btn-turquoise);
+ background-color: var(--btn-turquoise);
}
.font-white {
- color: var(--font-white);
+ color: var(--font-white);
}
.btn-yellow {
- background-color: var(--selected-color);
+ background-color: var(--selected-color);
}
@media (max-width: 1300px) {
- button {
- padding: var(--btn-padding-med);
- /* font-size: 90px; */
- }
-
- .material-icons {
- font-size: larger;
- }
-
- .under-video-area button {
- /* font-size: 30px; */
- padding: var(--btn-padding);
- gap: 10px;
- }
+ button {
+ padding: var(--btn-padding-med);
+ /* font-size: 90px; */
+ }
+
+ .material-icons {
+ font-size: larger;
+ }
+
+ .under-video-area button {
+ /* font-size: 30px; */
+ padding: var(--btn-padding);
+ gap: 10px;
+ }
}
diff --git a/src/pages/operator/html/index.html b/src/pages/operator/html/index.html
index 0716ea62..b7956157 100644
--- a/src/pages/operator/html/index.html
+++ b/src/pages/operator/html/index.html
@@ -1,23 +1,23 @@
-
-
- Stretch Web Interface
-
-
-
-
-
-
+
+
+ Stretch Web Interface
+
+
+
+
+
+
diff --git a/src/pages/operator/tsx/MobileOperator.tsx b/src/pages/operator/tsx/MobileOperator.tsx
index 7173844c..a0a93971 100644
--- a/src/pages/operator/tsx/MobileOperator.tsx
+++ b/src/pages/operator/tsx/MobileOperator.tsx
@@ -1,29 +1,29 @@
import React, { PointerEventHandler, useState } from "react";
import {
- ActionMode,
- ButtonPadIdMobile,
- CameraViewId,
- ComponentDefinition,
- ComponentType,
- MapDefinition,
+ ActionMode,
+ ButtonPadIdMobile,
+ CameraViewId,
+ ComponentDefinition,
+ ComponentType,
+ MapDefinition,
} from "./utils/component_definitions";
import {
- className,
- ActionState as MoveBaseState,
- RemoteStream,
+ className,
+ ActionState as MoveBaseState,
+ RemoteStream,
} from "shared/util";
import {
- buttonFunctionProvider,
- hasBetaTeleopKit,
- stretchTool,
- movementRecorderFunctionProvider,
- underMapFunctionProvider,
- underVideoFunctionProvider,
+ buttonFunctionProvider,
+ hasBetaTeleopKit,
+ stretchTool,
+ movementRecorderFunctionProvider,
+ underMapFunctionProvider,
+ underVideoFunctionProvider,
} from ".";
import {
- ButtonPadButton,
- ButtonState,
- ButtonStateMap,
+ ButtonPadButton,
+ ButtonState,
+ ButtonStateMap,
} from "./function_providers/ButtonFunctionProvider";
import { StorageHandler } from "./storage_handler/StorageHandler";
import { FunctionProvider } from "./function_providers/FunctionProvider";
@@ -37,8 +37,8 @@ import { Map } from "./layout_components/Map";
import { TabGroup } from "./basic_components/TabGroup";
import { RadioFunctions, RadioGroup } from "./basic_components/RadioGroup";
import {
- MovementRecorder,
- MovementRecorderFunction,
+ MovementRecorder,
+ MovementRecorderFunction,
} from "./layout_components/MovementRecorder";
import { CheckToggleButton } from "./basic_components/CheckToggleButton";
import { UnderVideoButton } from "./function_providers/UnderVideoFunctionProvider";
@@ -46,326 +46,336 @@ import { Alert } from "./basic_components/Alert";
/** Operator interface webpage */
export const MobileOperator = (props: {
- remoteStreams: Map;
- storageHandler: StorageHandler;
+ remoteStreams: Map;
+ storageHandler: StorageHandler;
}) => {
- const [buttonCollision, setButtonCollision] = React.useState<
- ButtonPadButton[]
- >([]);
- const [moveBaseState, setMoveBaseState] = React.useState();
- const [cameraID, setCameraID] = React.useState(
- CameraViewId.realsense,
- );
- const [velocityScale, setVelocityScale] = React.useState(0.8);
- const [hideMap, setHideMap] = React.useState(true);
- const [hideControls, setHideControls] = React.useState(false);
- const [activeMainGroupTab, setActiveMainGroupTab] = React.useState(0);
- const [activeControlTab, setActiveControlTab] = React.useState(0);
- const [isRecording, setIsRecording] = React.useState();
- const [depthSensing, setDepthSensing] = React.useState(false);
- const [showAlert, setShowAlert] = React.useState(true);
+ const [buttonCollision, setButtonCollision] = React.useState<
+ ButtonPadButton[]
+ >([]);
+ const [moveBaseState, setMoveBaseState] = React.useState();
+ const [cameraID, setCameraID] = React.useState(
+ CameraViewId.realsense,
+ );
+ const [velocityScale, setVelocityScale] = React.useState(0.8);
+ const [hideMap, setHideMap] = React.useState(true);
+ const [hideControls, setHideControls] = React.useState(false);
+ const [activeMainGroupTab, setActiveMainGroupTab] =
+ React.useState(0);
+ const [activeControlTab, setActiveControlTab] = React.useState(0);
+ const [isRecording, setIsRecording] = React.useState();
+ const [depthSensing, setDepthSensing] = React.useState(false);
+ const [showAlert, setShowAlert] = React.useState(true);
- React.useEffect(() => {
- setTimeout(function () {
- setShowAlert(false);
- }, 5000);
- }, []);
+ React.useEffect(() => {
+ setTimeout(function () {
+ setShowAlert(false);
+ }, 5000);
+ }, []);
- FunctionProvider.actionMode = ActionMode.PressAndHold;
+ FunctionProvider.actionMode = ActionMode.PressAndHold;
- // Just used as a flag to force the operator to rerender when the button state map
- // has been updated
- const [buttonStateMapRerender, setButtonStateMapRerender] =
- React.useState(false);
- const buttonStateMap = React.useRef();
- function operatorCallback(bsm: ButtonStateMap) {
- let collisionButtons: ButtonPadButton[] = [];
- bsm.forEach((state, button) => {
- if (state == ButtonState.Collision) collisionButtons.push(button);
- });
- setButtonCollision(collisionButtons);
- if (bsm !== buttonStateMap.current) {
- buttonStateMap.current = bsm;
- setButtonStateMapRerender(!buttonStateMapRerender);
+ // Just used as a flag to force the operator to rerender when the button state map
+ // has been updated
+ const [buttonStateMapRerender, setButtonStateMapRerender] =
+ React.useState(false);
+ const buttonStateMap = React.useRef();
+ function operatorCallback(bsm: ButtonStateMap) {
+ let collisionButtons: ButtonPadButton[] = [];
+ bsm.forEach((state, button) => {
+ if (state == ButtonState.Collision) collisionButtons.push(button);
+ });
+ setButtonCollision(collisionButtons);
+ if (bsm !== buttonStateMap.current) {
+ buttonStateMap.current = bsm;
+ setButtonStateMapRerender(!buttonStateMapRerender);
+ }
}
- }
- buttonFunctionProvider.setOperatorCallback(operatorCallback);
+ buttonFunctionProvider.setOperatorCallback(operatorCallback);
- // Just used as a flag to force the operator to rerender when the tablet orientation
- // changes.
- const [tabletOrientationRerender, setTabletOrientationRerender] =
- React.useState(false);
- underVideoFunctionProvider.setTabletOrientationOperatorCallback((_) => {
- setTabletOrientationRerender(!tabletOrientationRerender);
- });
+ // Just used as a flag to force the operator to rerender when the tablet orientation
+ // changes.
+ const [tabletOrientationRerender, setTabletOrientationRerender] =
+ React.useState(false);
+ underVideoFunctionProvider.setTabletOrientationOperatorCallback((_) => {
+ setTabletOrientationRerender(!tabletOrientationRerender);
+ });
- function moveBaseStateCallback(state: MoveBaseState) {
- setMoveBaseState(state);
- }
- underMapFunctionProvider.setOperatorCallback(moveBaseStateCallback);
- let moveBaseAlertTimeout: NodeJS.Timeout;
- React.useEffect(() => {
- if (moveBaseState && moveBaseState.alertType != "info") {
- if (moveBaseAlertTimeout) clearTimeout(moveBaseAlertTimeout);
- moveBaseAlertTimeout = setTimeout(() => {
- setMoveBaseState(undefined);
- }, 5000);
+ function moveBaseStateCallback(state: MoveBaseState) {
+ setMoveBaseState(state);
}
- }, [moveBaseState]);
+ underMapFunctionProvider.setOperatorCallback(moveBaseStateCallback);
+ let moveBaseAlertTimeout: NodeJS.Timeout;
+ React.useEffect(() => {
+ if (moveBaseState && moveBaseState.alertType != "info") {
+ if (moveBaseAlertTimeout) clearTimeout(moveBaseAlertTimeout);
+ moveBaseAlertTimeout = setTimeout(() => {
+ setMoveBaseState(undefined);
+ }, 5000);
+ }
+ }, [moveBaseState]);
- let remoteStreams = props.remoteStreams;
+ let remoteStreams = props.remoteStreams;
- /** State passed from the operator and shared by all components */
- const sharedState: SharedState = {
- customizing: false,
- onSelect: () => {},
- remoteStreams: remoteStreams,
- selectedPath: "deselected",
- dropZoneState: {
- onDrop: () => {},
- selectedDefinition: undefined,
- },
- buttonStateMap: buttonStateMap.current,
- hideLabels: false,
- hasBetaTeleopKit: hasBetaTeleopKit,
- stretchTool: stretchTool,
- };
+ /** State passed from the operator and shared by all components */
+ const sharedState: SharedState = {
+ customizing: false,
+ onSelect: () => {},
+ remoteStreams: remoteStreams,
+ selectedPath: "deselected",
+ dropZoneState: {
+ onDrop: () => {},
+ selectedDefinition: undefined,
+ },
+ buttonStateMap: buttonStateMap.current,
+ hideLabels: false,
+ hasBetaTeleopKit: hasBetaTeleopKit,
+ stretchTool: stretchTool,
+ };
- function updateScreens() {
- if (hideMap) {
- setHideMap(false);
- setHideControls(true);
- } else {
- setHideControls(false);
- setHideMap(true);
+ function updateScreens() {
+ if (hideMap) {
+ setHideMap(false);
+ setHideControls(true);
+ } else {
+ setHideControls(false);
+ setHideMap(true);
+ }
}
- }
- const swipeHandlers = Swipe({
- onSwipedLeft: () => updateScreens(),
- onSwipedRight: () => updateScreens(),
- });
+ const swipeHandlers = Swipe({
+ onSwipedLeft: () => updateScreens(),
+ onSwipedRight: () => updateScreens(),
+ });
- const driveMode = (show: boolean) => {
- return show ? (
-
- {/* {
+ return show ? (
+
+ {/* */}
-
-
- ) : (
- <>>
- );
- };
+
+
+ ) : (
+ <>>
+ );
+ };
- const armMode = (show: boolean) => {
- return show ? (
-
-
-
- ) : (
- <>>
- );
- };
+ const armMode = (show: boolean) => {
+ return show ? (
+
+
+
+ ) : (
+ <>>
+ );
+ };
- const gripperMode = (show: boolean) => {
- return show ? (
-
-
-
- ) : (
- <>>
- );
- };
+ const gripperMode = (show: boolean) => {
+ return show ? (
+
+
+
+ ) : (
+ <>>
+ );
+ };
- const ControlModes = () => {
- return (
- <>
-
- Slow
- {
- setVelocityScale(FunctionProvider.velocityScale);
- }}
- onChange={(event) => {
- FunctionProvider.velocityScale = Number(event.target.value);
- }}
- />
- Fast
-
- setActiveControlTab(index)}
- pill={true}
- key={"controls-group"}
- />
- >
- );
- };
+ const ControlModes = () => {
+ return (
+ <>
+
+ Slow
+ {
+ setVelocityScale(FunctionProvider.velocityScale);
+ }}
+ onChange={(event) => {
+ FunctionProvider.velocityScale = Number(
+ event.target.value,
+ );
+ }}
+ />
+ Fast
+
+ setActiveControlTab(index)}
+ pill={true}
+ key={"controls-group"}
+ />
+ >
+ );
+ };
- const controlModes = (show: boolean) => {
- return show ? : <>>;
- };
- const recordingList = (show: boolean) => {
- return (
-
- );
- };
+ const controlModes = (show: boolean) => {
+ return show ? : <>>;
+ };
+ const recordingList = (show: boolean) => {
+ return (
+
+ );
+ };
- return (
- e.preventDefault()}>
-
- {showAlert ? (
-
-
- Beta feature, use at your own risk
-
-
- ) : (
- <>>
- )}
-
-
- {
- if (cameraID == CameraViewId.realsense)
- setCameraID(CameraViewId.overhead);
- else if (cameraID == CameraViewId.overhead)
- setCameraID(CameraViewId.gripper);
- else if (cameraID == CameraViewId.gripper)
- setCameraID(CameraViewId.realsense);
- }}
- >
- photo_camera
-
- {
- setHideMap(false);
- setHideControls(true);
- }}
- >
- map
-
-
- {cameraID == CameraViewId.realsense && (
-
-
{
- setDepthSensing(!depthSensing);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.DepthSensing,
- ).onCheck!(!depthSensing);
- }}
- label="Depth Sensing"
- />
+ return (
+ e.preventDefault()}>
+
+ {showAlert ? (
+
+
+ Beta feature, use at your own risk
+
+
+ ) : (
+ <>>
+ )}
+
+
+ {
+ if (cameraID == CameraViewId.realsense)
+ setCameraID(CameraViewId.overhead);
+ else if (cameraID == CameraViewId.overhead)
+ setCameraID(CameraViewId.gripper);
+ else if (cameraID == CameraViewId.gripper)
+ setCameraID(CameraViewId.realsense);
+ }}
+ >
+ photo_camera
+
+ {
+ setHideMap(false);
+ setHideControls(true);
+ }}
+ >
+ map
+
+
+ {cameraID == CameraViewId.realsense && (
+
+ {
+ setDepthSensing(!depthSensing);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.DepthSensing,
+ ).onCheck!(!depthSensing);
+ }}
+ label="Depth Sensing"
+ />
+
+ )}
+
{
+ setIsRecording(!isRecording);
+ }}
+ >
+ {!isRecording ? (
+ <>
+
+ radio_button_checked
+
+ Record
+ >
+ ) : (
+ <>
+
+
+ Stop Recording
+ >
+ )}
+
+ {/*
*/}
+
+
+
+
+ setActiveMainGroupTab(index)
+ }
+ pill={false}
+ key={"main-group"}
+ />
+
+ {/*
*/}
+
+
+ {
+ setHideMap(true);
+ setHideControls(false);
+ }}
+ >
+ photo_camera
+
+
+
+
- )}
-
{
- setIsRecording(!isRecording);
- }}
- >
- {!isRecording ? (
- <>
- radio_button_checked
- Record
- >
- ) : (
- <>
-
-
- Stop Recording
- >
- )}
-
- {/*
*/}
-
-
-
-
setActiveMainGroupTab(index)}
- pill={false}
- key={"main-group"}
- />
- {/*
*/}
-
-
- {
- setHideMap(true);
- setHideControls(false);
- }}
- >
- photo_camera
-
-
-
-
-
-
- );
+ );
};
diff --git a/src/pages/operator/tsx/Operator.tsx b/src/pages/operator/tsx/Operator.tsx
index 309427d5..9ae18d8a 100644
--- a/src/pages/operator/tsx/Operator.tsx
+++ b/src/pages/operator/tsx/Operator.tsx
@@ -6,34 +6,34 @@ import { CustomizeButton } from "./static_components/CustomizeButton";
import { GlobalOptionsProps, Sidebar } from "./static_components/Sidebar";
import { SharedState } from "./layout_components/CustomizableComponent";
import {
- ActionMode,
- ComponentDefinition,
- LayoutDefinition,
+ ActionMode,
+ ComponentDefinition,
+ LayoutDefinition,
} from "./utils/component_definitions";
import { className, ActionState, RemoteStream, RobotPose } from "shared/util";
import {
- buttonFunctionProvider,
- underMapFunctionProvider,
- underVideoFunctionProvider,
- hasBetaTeleopKit,
- stretchTool,
+ buttonFunctionProvider,
+ underMapFunctionProvider,
+ underVideoFunctionProvider,
+ hasBetaTeleopKit,
+ stretchTool,
} from ".";
import {
- ButtonPadButton,
- ButtonState,
- ButtonStateMap,
+ ButtonPadButton,
+ ButtonState,
+ ButtonStateMap,
} from "./function_providers/ButtonFunctionProvider";
import { Dropdown } from "./basic_components/Dropdown";
import {
- DEFAULT_LAYOUTS,
- DefaultLayoutName,
- StorageHandler,
+ DEFAULT_LAYOUTS,
+ DefaultLayoutName,
+ StorageHandler,
} from "./storage_handler/StorageHandler";
import { FunctionProvider } from "./function_providers/FunctionProvider";
import {
- addToLayout,
- moveInLayout,
- removeFromLayout,
+ addToLayout,
+ moveInLayout,
+ removeFromLayout,
} from "./utils/layout_helpers";
import { MovementRecorder } from "./layout_components/MovementRecorder";
import { Alert } from "./basic_components/Alert";
@@ -42,359 +42,365 @@ import { TextToSpeech } from "./layout_components/TextToSpeech";
/** Operator interface webpage */
export const Operator = (props: {
- remoteStreams: Map
;
- layout: LayoutDefinition;
- storageHandler: StorageHandler;
+ remoteStreams: Map;
+ layout: LayoutDefinition;
+ storageHandler: StorageHandler;
}) => {
- const [customizing, setCustomizing] = React.useState(false);
- const [selectedPath, setSelectedPath] = React.useState(
- undefined,
- );
- const [selectedDefinition, setSelectedDef] = React.useState<
- ComponentDefinition | undefined
- >(undefined);
- const [velocityScale, setVelocityScale] = React.useState(
- FunctionProvider.velocityScale,
- );
- const [buttonCollision, setButtonCollision] = React.useState<
- ButtonPadButton[]
- >([]);
- const [moveBaseState, setMoveBaseState] = React.useState();
- const [moveToPregraspState, setMoveToPregraspState] =
- React.useState();
+ const [customizing, setCustomizing] = React.useState(false);
+ const [selectedPath, setSelectedPath] = React.useState(
+ undefined,
+ );
+ const [selectedDefinition, setSelectedDef] = React.useState<
+ ComponentDefinition | undefined
+ >(undefined);
+ const [velocityScale, setVelocityScale] = React.useState(
+ FunctionProvider.velocityScale,
+ );
+ const [buttonCollision, setButtonCollision] = React.useState<
+ ButtonPadButton[]
+ >([]);
+ const [moveBaseState, setMoveBaseState] = React.useState();
+ const [moveToPregraspState, setMoveToPregraspState] =
+ React.useState();
- const layout = React.useRef(props.layout);
+ const layout = React.useRef(props.layout);
- // Just used as a flag to force the operator to rerender when the button state map
- // has been updated
- const [buttonStateMapRerender, setButtonStateMapRerender] =
- React.useState(false);
- const buttonStateMap = React.useRef();
- function operatorCallback(bsm: ButtonStateMap) {
- let collisionButtons: ButtonPadButton[] = [];
- bsm.forEach((state, button) => {
- if (state == ButtonState.Collision) collisionButtons.push(button);
- });
- setButtonCollision(collisionButtons);
- buttonStateMap.current = bsm;
- setButtonStateMapRerender(!buttonStateMapRerender);
- }
- buttonFunctionProvider.setOperatorCallback(operatorCallback);
+ // Just used as a flag to force the operator to rerender when the button state map
+ // has been updated
+ const [buttonStateMapRerender, setButtonStateMapRerender] =
+ React.useState(false);
+ const buttonStateMap = React.useRef();
+ function operatorCallback(bsm: ButtonStateMap) {
+ let collisionButtons: ButtonPadButton[] = [];
+ bsm.forEach((state, button) => {
+ if (state == ButtonState.Collision) collisionButtons.push(button);
+ });
+ setButtonCollision(collisionButtons);
+ buttonStateMap.current = bsm;
+ setButtonStateMapRerender(!buttonStateMapRerender);
+ }
+ buttonFunctionProvider.setOperatorCallback(operatorCallback);
- // Just used as a flag to force the operator to rerender when the tablet orientation
- // changes.
- const [tabletOrientationRerender, setTabletOrientationRerender] =
- React.useState(false);
- underVideoFunctionProvider.setTabletOrientationOperatorCallback((_) => {
- setTabletOrientationRerender(!tabletOrientationRerender);
- });
+ // Just used as a flag to force the operator to rerender when the tablet orientation
+ // changes.
+ const [tabletOrientationRerender, setTabletOrientationRerender] =
+ React.useState(false);
+ underVideoFunctionProvider.setTabletOrientationOperatorCallback((_) => {
+ setTabletOrientationRerender(!tabletOrientationRerender);
+ });
- // Callback for when the move base state is updated (e.g., the ROS2 action returns)
- // Used to render alerts to the operator.
- function moveBaseStateCallback(state: ActionState) {
- setMoveBaseState(state);
- }
- underMapFunctionProvider.setOperatorCallback(moveBaseStateCallback);
- let moveBaseAlertTimeout: NodeJS.Timeout;
- React.useEffect(() => {
- if (moveBaseState && moveBaseState.alert_type != "info") {
- if (moveBaseAlertTimeout) clearTimeout(moveBaseAlertTimeout);
- moveBaseAlertTimeout = setTimeout(() => {
- setMoveBaseState(undefined);
- }, 5000);
+ // Callback for when the move base state is updated (e.g., the ROS2 action returns)
+ // Used to render alerts to the operator.
+ function moveBaseStateCallback(state: ActionState) {
+ setMoveBaseState(state);
}
- }, [moveBaseState]);
+ underMapFunctionProvider.setOperatorCallback(moveBaseStateCallback);
+ let moveBaseAlertTimeout: NodeJS.Timeout;
+ React.useEffect(() => {
+ if (moveBaseState && moveBaseState.alert_type != "info") {
+ if (moveBaseAlertTimeout) clearTimeout(moveBaseAlertTimeout);
+ moveBaseAlertTimeout = setTimeout(() => {
+ setMoveBaseState(undefined);
+ }, 5000);
+ }
+ }, [moveBaseState]);
- // Callback for when the move to pregrasp state is updated (e.g., the ROS2 action returns)
- // Used to render alerts to the operator.
- function moveToPregraspStateCallback(state: ActionState) {
- setMoveToPregraspState(state);
- }
- underVideoFunctionProvider.setMoveToPregraspOperatorCallback(
- moveToPregraspStateCallback,
- );
- let moveToPregraspAlertTimeout: NodeJS.Timeout;
- React.useEffect(() => {
- if (moveToPregraspState && moveToPregraspState.alert_type != "info") {
- if (moveToPregraspAlertTimeout) clearTimeout(moveToPregraspAlertTimeout);
- moveToPregraspAlertTimeout = setTimeout(() => {
- setMoveToPregraspState(undefined);
- }, 5000);
+ // Callback for when the move to pregrasp state is updated (e.g., the ROS2 action returns)
+ // Used to render alerts to the operator.
+ function moveToPregraspStateCallback(state: ActionState) {
+ setMoveToPregraspState(state);
}
- }, [moveToPregraspState]);
+ underVideoFunctionProvider.setMoveToPregraspOperatorCallback(
+ moveToPregraspStateCallback,
+ );
+ let moveToPregraspAlertTimeout: NodeJS.Timeout;
+ React.useEffect(() => {
+ if (moveToPregraspState && moveToPregraspState.alert_type != "info") {
+ if (moveToPregraspAlertTimeout)
+ clearTimeout(moveToPregraspAlertTimeout);
+ moveToPregraspAlertTimeout = setTimeout(() => {
+ setMoveToPregraspState(undefined);
+ }, 5000);
+ }
+ }, [moveToPregraspState]);
- let remoteStreams = props.remoteStreams;
+ let remoteStreams = props.remoteStreams;
- /** Rerenders the operator */
- function updateLayout() {
- console.log("update layout");
- setButtonStateMapRerender(!buttonStateMapRerender);
- setTabletOrientationRerender(!tabletOrientationRerender);
- }
+ /** Rerenders the operator */
+ function updateLayout() {
+ console.log("update layout");
+ setButtonStateMapRerender(!buttonStateMapRerender);
+ setTabletOrientationRerender(!tabletOrientationRerender);
+ }
- /**
- * Updates the action mode in the layout (visually) and in the function
- * provider (functionally).
- */
- function setActionMode(actionMode: ActionMode) {
- layout.current.actionMode = actionMode;
- FunctionProvider.actionMode = actionMode;
- props.storageHandler.saveCurrentLayout(layout.current);
- updateLayout();
- }
+ /**
+ * Updates the action mode in the layout (visually) and in the function
+ * provider (functionally).
+ */
+ function setActionMode(actionMode: ActionMode) {
+ layout.current.actionMode = actionMode;
+ FunctionProvider.actionMode = actionMode;
+ props.storageHandler.saveCurrentLayout(layout.current);
+ updateLayout();
+ }
- /**
- * Sets the movement recorder component to display or hidden.
- *
- * @param displayMovementRecorder if the movement recorder component at the
- * top of the operator body should be displayed
- */
- function setDisplayMovementRecorder(displayMovementRecorder: boolean) {
- layout.current.displayMovementRecorder = displayMovementRecorder;
- updateLayout();
- }
+ /**
+ * Sets the movement recorder component to display or hidden.
+ *
+ * @param displayMovementRecorder if the movement recorder component at the
+ * top of the operator body should be displayed
+ */
+ function setDisplayMovementRecorder(displayMovementRecorder: boolean) {
+ layout.current.displayMovementRecorder = displayMovementRecorder;
+ updateLayout();
+ }
- /**
- * Sets the text-to-speech component to display or hidden.
- *
- * @param displayTextToSpeech whether the text-to-speech component should
- * be displayed.
- */
- function setDisplayTextToSpeech(displayTextToSpeech: boolean) {
- layout.current.displayTextToSpeech = displayTextToSpeech;
- updateLayout();
- }
+ /**
+ * Sets the text-to-speech component to display or hidden.
+ *
+ * @param displayTextToSpeech whether the text-to-speech component should
+ * be displayed.
+ */
+ function setDisplayTextToSpeech(displayTextToSpeech: boolean) {
+ layout.current.displayTextToSpeech = displayTextToSpeech;
+ updateLayout();
+ }
- /**
- * Sets the display labels property to display or hidden.
- *
- * @param displayLabels if the button text labels should be displayed
- */
- function setDisplayLabels(displayLabels: boolean) {
- layout.current.displayLabels = displayLabels;
- updateLayout();
- }
+ /**
+ * Sets the display labels property to display or hidden.
+ *
+ * @param displayLabels if the button text labels should be displayed
+ */
+ function setDisplayLabels(displayLabels: boolean) {
+ layout.current.displayLabels = displayLabels;
+ updateLayout();
+ }
- /**
- * Callback when the user clicks on a drop zone, moves the active component
- * into the drop zone
- * @param path path to the clicked drop zone
- */
- function handleDrop(path: string) {
- console.log("handleDrop", path);
- if (!selectedDefinition)
- throw Error("Active definition undefined on drop event");
- let newPath: string = path;
- if (!selectedPath) {
- // New element not already in the layout
- newPath = addToLayout(selectedDefinition, path, layout.current);
- } else {
- newPath = moveInLayout(selectedPath, path, layout.current);
+ /**
+ * Callback when the user clicks on a drop zone, moves the active component
+ * into the drop zone
+ * @param path path to the clicked drop zone
+ */
+ function handleDrop(path: string) {
+ console.log("handleDrop", path);
+ if (!selectedDefinition)
+ throw Error("Active definition undefined on drop event");
+ let newPath: string = path;
+ if (!selectedPath) {
+ // New element not already in the layout
+ newPath = addToLayout(selectedDefinition, path, layout.current);
+ } else {
+ newPath = moveInLayout(selectedPath, path, layout.current);
+ }
+ setSelectedPath(newPath);
+ console.log("new active path", newPath);
+ updateLayout();
}
- setSelectedPath(newPath);
- console.log("new active path", newPath);
- updateLayout();
- }
- /**
- * Callback when a component is selected during customization
- * @param path path to the selected component
- * @param def definition of the selected component
- */
- function handleSelect(def: ComponentDefinition, path?: string) {
- console.log("selected", path);
- if (!customizing) return;
+ /**
+ * Callback when a component is selected during customization
+ * @param path path to the selected component
+ * @param def definition of the selected component
+ */
+ function handleSelect(def: ComponentDefinition, path?: string) {
+ console.log("selected", path);
+ if (!customizing) return;
- // If reselected the same component at the same path, or the same component
- // without a path from the sidebar, then unactivate it
- const pathsMatch = selectedPath && selectedPath == path;
- const defsMatch =
- !selectedPath &&
- def.type === selectedDefinition?.type &&
- def.id === selectedDefinition?.id;
- if (pathsMatch || defsMatch) {
- setSelectedDef(undefined);
- setSelectedPath(undefined);
- return;
+ // If reselected the same component at the same path, or the same component
+ // without a path from the sidebar, then unactivate it
+ const pathsMatch = selectedPath && selectedPath == path;
+ const defsMatch =
+ !selectedPath &&
+ def.type === selectedDefinition?.type &&
+ def.id === selectedDefinition?.id;
+ if (pathsMatch || defsMatch) {
+ setSelectedDef(undefined);
+ setSelectedPath(undefined);
+ return;
+ }
+
+ // Activate the selected component
+ setSelectedDef(def);
+ setSelectedPath(path);
}
- // Activate the selected component
- setSelectedDef(def);
- setSelectedPath(path);
- }
+ /** Callback when the delete button in the sidebar is clicked */
+ function handleDelete() {
+ if (!selectedPath)
+ throw Error("handleDelete called when selectedPath is undefined");
+ removeFromLayout(selectedPath, layout.current);
+ updateLayout();
+ setSelectedPath(undefined);
+ setSelectedDef(undefined);
+ }
- /** Callback when the delete button in the sidebar is clicked */
- function handleDelete() {
- if (!selectedPath)
- throw Error("handleDelete called when selectedPath is undefined");
- removeFromLayout(selectedPath, layout.current);
- updateLayout();
- setSelectedPath(undefined);
- setSelectedDef(undefined);
- }
+ /**
+ * Callback when the customization button is clicked.
+ */
+ const handleToggleCustomize = () => {
+ if (customizing) {
+ console.log("saving layout");
+ props.storageHandler.saveCurrentLayout(layout.current);
+ }
+ setCustomizing(!customizing);
+ setSelectedDef(undefined);
+ setSelectedPath(undefined);
+ };
- /**
- * Callback when the customization button is clicked.
- */
- const handleToggleCustomize = () => {
- if (customizing) {
- console.log("saving layout");
- props.storageHandler.saveCurrentLayout(layout.current);
+ /** Un-select current component when click inside of header */
+ function handleClickHeader() {
+ setSelectedDef(undefined);
+ setSelectedPath(undefined);
}
- setCustomizing(!customizing);
- setSelectedDef(undefined);
- setSelectedPath(undefined);
- };
- /** Un-select current component when click inside of header */
- function handleClickHeader() {
- setSelectedDef(undefined);
- setSelectedPath(undefined);
- }
+ /** State passed from the operator and shared by all components */
+ const sharedState: SharedState = {
+ customizing: customizing,
+ onSelect: handleSelect,
+ remoteStreams: remoteStreams,
+ selectedPath: selectedPath,
+ dropZoneState: {
+ onDrop: handleDrop,
+ selectedDefinition: selectedDefinition,
+ },
+ buttonStateMap: buttonStateMap.current,
+ hideLabels: !layout.current.displayLabels,
+ hasBetaTeleopKit: hasBetaTeleopKit,
+ stretchTool: stretchTool,
+ };
- /** State passed from the operator and shared by all components */
- const sharedState: SharedState = {
- customizing: customizing,
- onSelect: handleSelect,
- remoteStreams: remoteStreams,
- selectedPath: selectedPath,
- dropZoneState: {
- onDrop: handleDrop,
- selectedDefinition: selectedDefinition,
- },
- buttonStateMap: buttonStateMap.current,
- hideLabels: !layout.current.displayLabels,
- hasBetaTeleopKit: hasBetaTeleopKit,
- stretchTool: stretchTool,
- };
+ /** Properties for the global options area of the sidebar */
+ const globalOptionsProps: GlobalOptionsProps = {
+ displayMovementRecorder: layout.current.displayMovementRecorder,
+ displayTextToSpeech: layout.current.displayTextToSpeech,
+ displayLabels: layout.current.displayLabels,
+ setDisplayMovementRecorder: setDisplayMovementRecorder,
+ setDisplayTextToSpeech: setDisplayTextToSpeech,
+ setDisplayLabels: setDisplayLabels,
+ defaultLayouts: Object.keys(DEFAULT_LAYOUTS),
+ customLayouts: props.storageHandler.getCustomLayoutNames(),
+ loadLayout: (layoutName: string, dflt: boolean) => {
+ layout.current = dflt
+ ? props.storageHandler.loadDefaultLayout(
+ layoutName as DefaultLayoutName,
+ )
+ : props.storageHandler.loadCustomLayout(layoutName);
+ updateLayout();
+ },
+ saveLayout: (layoutName: string) => {
+ props.storageHandler.saveCustomLayout(layout.current, layoutName);
+ },
+ };
- /** Properties for the global options area of the sidebar */
- const globalOptionsProps: GlobalOptionsProps = {
- displayMovementRecorder: layout.current.displayMovementRecorder,
- displayTextToSpeech: layout.current.displayTextToSpeech,
- displayLabels: layout.current.displayLabels,
- setDisplayMovementRecorder: setDisplayMovementRecorder,
- setDisplayTextToSpeech: setDisplayTextToSpeech,
- setDisplayLabels: setDisplayLabels,
- defaultLayouts: Object.keys(DEFAULT_LAYOUTS),
- customLayouts: props.storageHandler.getCustomLayoutNames(),
- loadLayout: (layoutName: string, dflt: boolean) => {
- layout.current = dflt
- ? props.storageHandler.loadDefaultLayout(
- layoutName as DefaultLayoutName,
- )
- : props.storageHandler.loadCustomLayout(layoutName);
- updateLayout();
- },
- saveLayout: (layoutName: string) => {
- props.storageHandler.saveCustomLayout(layout.current, layoutName);
- },
- };
+ const actionModes = Object.values(ActionMode);
- const actionModes = Object.values(ActionMode);
-
- return (
-
-
- {
-
-
0,
- fadeOut: buttonCollision.length == 0,
- })}
- >
-
-
- {buttonCollision.length > 0
- ? buttonCollision.join(", ") + " in collision!"
- : ""}
-
-
-
-
- }
- {moveBaseState && (
-
-
-
+
+ {
+
+
0,
+ fadeOut: buttonCollision.length == 0,
+ })}
+ >
+
+
+ {buttonCollision.length > 0
+ ? buttonCollision.join(", ") +
+ " in collision!"
+ : ""}
+
+
+
+
+ }
+ {moveBaseState && (
+
+ )}
+ {moveToPregraspState && (
+
+ )}
+
+
+
+
+
-
-
- )}
- {moveToPregraspState && (
-
- )}
-
-
-
-
-
-
- );
+ );
};
diff --git a/src/pages/operator/tsx/basic_components/AccordionSelect.tsx b/src/pages/operator/tsx/basic_components/AccordionSelect.tsx
index 889e89da..af1b1eac 100644
--- a/src/pages/operator/tsx/basic_components/AccordionSelect.tsx
+++ b/src/pages/operator/tsx/basic_components/AccordionSelect.tsx
@@ -3,61 +3,61 @@ import "operator/css/basic_components.css";
import { className } from "shared/util";
export const AccordionSelect = (props: {
- title: string;
- possibleOptions: T[];
- backgroundColor?: string;
- onChange: (selectedIndex: number) => void;
- toggleAccordianCallback?: () => void;
+ title: string;
+ possibleOptions: T[];
+ backgroundColor?: string;
+ onChange: (selectedIndex: number) => void;
+ toggleAccordianCallback?: () => void;
}) => {
- const [active, setActiveState] = useState(false);
- const [height, setHeightState] = useState("0px");
- const [rotate, setRotateState] = useState("accordion_icon");
- const content = useRef(null);
+ const [active, setActiveState] = useState(false);
+ const [height, setHeightState] = useState("0px");
+ const [rotate, setRotateState] = useState("accordion_icon");
+ const content = useRef(null);
- function toggleAccordion() {
- if (props.toggleAccordianCallback) {
- props.toggleAccordianCallback();
+ function toggleAccordion() {
+ if (props.toggleAccordianCallback) {
+ props.toggleAccordianCallback();
+ }
+ setActiveState(active ? false : true);
+ setHeightState(active ? "0px" : `${content.current.scrollHeight}px`);
+ setRotateState(active ? "accordion_icon" : "accordion_icon rotate");
+ }
+
+ function mapFunc(option: T, idx: number) {
+ return (
+ {
+ props.onChange(idx);
+ toggleAccordion();
+ }}
+ >
+ {option}
+
+ );
}
- setActiveState(active ? false : true);
- setHeightState(active ? "0px" : `${content.current.scrollHeight}px`);
- setRotateState(active ? "accordion_icon" : "accordion_icon rotate");
- }
- function mapFunc(option: T, idx: number) {
return (
- {
- props.onChange(idx);
- toggleAccordion();
- }}
- >
- {option}
-
+
+
+ {props.title}
+ expand_more
+
+
+
{props.possibleOptions.map(mapFunc)}
+
+
);
- }
-
- return (
-
-
- {props.title}
- expand_more
-
-
-
{props.possibleOptions.map(mapFunc)}
-
-
- );
};
diff --git a/src/pages/operator/tsx/basic_components/Alert.tsx b/src/pages/operator/tsx/basic_components/Alert.tsx
index 87c14961..086b7817 100644
--- a/src/pages/operator/tsx/basic_components/Alert.tsx
+++ b/src/pages/operator/tsx/basic_components/Alert.tsx
@@ -5,26 +5,26 @@ import "operator/css/Alert.css";
// https://blog.logrocket.com/create-custom-react-alert-message/
export const Alert = (props: {
- children?: ReactElement>;
- type: string;
- message?: string;
+ children?: ReactElement>;
+ type: string;
+ message?: string;
}) => {
- const [isShow, setIsShow] = useState(true);
+ const [isShow, setIsShow] = useState(true);
- function renderElAlert() {
- return React.cloneElement(props.children!);
- }
+ function renderElAlert() {
+ return React.cloneElement(props.children!);
+ }
- React.useEffect(() => {
- setIsShow(true);
- }, [props]);
+ React.useEffect(() => {
+ setIsShow(true);
+ }, [props]);
- return (
-
- setIsShow(false)}>
- ×
-
- {props.children ? renderElAlert() : props.message}
-
- );
+ return (
+
+ setIsShow(false)}>
+ ×
+
+ {props.children ? renderElAlert() : props.message}
+
+ );
};
diff --git a/src/pages/operator/tsx/basic_components/CheckToggleButton.tsx b/src/pages/operator/tsx/basic_components/CheckToggleButton.tsx
index c2397e6e..69cee64d 100644
--- a/src/pages/operator/tsx/basic_components/CheckToggleButton.tsx
+++ b/src/pages/operator/tsx/basic_components/CheckToggleButton.tsx
@@ -7,17 +7,17 @@ import React from "react";
* Properties for {@link CheckToggleButton}
*/
type CheckToggleButtonProps = {
- /** Toggled on if true, toggled off if false. */
- checked: boolean;
- /**
- * Function when button is clicked, this should probably toggle the state
- * of `checked`
- */
- onClick: () => void;
- /**
- * Text to display on the button to the right of the checkbox.
- */
- label: string;
+ /** Toggled on if true, toggled off if false. */
+ checked: boolean;
+ /**
+ * Function when button is clicked, this should probably toggle the state
+ * of `checked`
+ */
+ onClick: () => void;
+ /**
+ * Text to display on the button to the right of the checkbox.
+ */
+ label: string;
};
/**
@@ -27,18 +27,18 @@ type CheckToggleButtonProps = {
* @param props {@link CheckToggleButtonProps}
*/
export const CheckToggleButton = (props: CheckToggleButtonProps) => {
- const { checked } = props;
- const icon = checked ? "check_box" : "check_box_outline_blank";
- return (
-
- {icon}
- {props.label}
-
- );
+ const { checked } = props;
+ const icon = checked ? "check_box" : "check_box_outline_blank";
+ return (
+
+ {icon}
+ {props.label}
+
+ );
};
diff --git a/src/pages/operator/tsx/basic_components/Dropdown.tsx b/src/pages/operator/tsx/basic_components/Dropdown.tsx
index 6178c212..6eb1f0b1 100644
--- a/src/pages/operator/tsx/basic_components/Dropdown.tsx
+++ b/src/pages/operator/tsx/basic_components/Dropdown.tsx
@@ -3,93 +3,93 @@ import { className } from "shared/util";
import "operator/css/basic_components.css";
export const Dropdown = (props: {
- onChange: (selectedIndex: number) => void;
- possibleOptions: T[];
- selectedIndex?: number;
- placeholderText?: string;
- showActive?: boolean;
- placement: string;
+ onChange: (selectedIndex: number) => void;
+ possibleOptions: T[];
+ selectedIndex?: number;
+ placeholderText?: string;
+ showActive?: boolean;
+ placement: string;
}) => {
- const [showDropdown, setShowDropdown] = React.useState(false);
- const [placement, setPlacement] = React.useState(props.placement);
- const inputRef = React.useRef(null);
- const dropdownPopupRef = React.useRef(null);
- if (props.selectedIndex === undefined && !props.placeholderText)
- throw Error("both selectedOption and placeholderText undefined");
+ const [showDropdown, setShowDropdown] = React.useState(false);
+ const [placement, setPlacement] = React.useState(props.placement);
+ const inputRef = React.useRef(null);
+ const dropdownPopupRef = React.useRef(null);
+ if (props.selectedIndex === undefined && !props.placeholderText)
+ throw Error("both selectedOption and placeholderText undefined");
- // Handler to close dropdown when click outside
- React.useEffect(() => {
- const handler = (e: any) => {
- if (inputRef.current && !inputRef.current.contains(e.target)) {
- setShowDropdown(false);
- }
- };
- if (showDropdown) {
- window.addEventListener("click", handler);
- return () => {
- window.removeEventListener("click", handler);
- };
- }
- });
+ // Handler to close dropdown when click outside
+ React.useEffect(() => {
+ const handler = (e: any) => {
+ if (inputRef.current && !inputRef.current.contains(e.target)) {
+ setShowDropdown(false);
+ }
+ };
+ if (showDropdown) {
+ window.addEventListener("click", handler);
+ return () => {
+ window.removeEventListener("click", handler);
+ };
+ }
+ });
- // Function to convert each possible option into a button
- function mapFunc(option: T, idx: number) {
- const active = idx === props.selectedIndex;
- if (active && !props.showActive) return null;
- return (
- {
- setShowDropdown(false);
- if (!active) props.onChange(idx);
- }}
- className={className("dropdown-option", { active })}
- >
- {option}
-
- );
- }
+ // Function to convert each possible option into a button
+ function mapFunc(option: T, idx: number) {
+ const active = idx === props.selectedIndex;
+ if (active && !props.showActive) return null;
+ return (
+ {
+ setShowDropdown(false);
+ if (!active) props.onChange(idx);
+ }}
+ className={className("dropdown-option", { active })}
+ >
+ {option}
+
+ );
+ }
- // Set the max-height of the popup to the screen height minus the top of the popup
- function resizeDropdownPopup() {
- if (dropdownPopupRef.current) {
- const top = dropdownPopupRef.current.getBoundingClientRect().top;
- dropdownPopupRef.current.style.maxHeight = `calc(100vh - ${top}px)`;
+ // Set the max-height of the popup to the screen height minus the top of the popup
+ function resizeDropdownPopup() {
+ if (dropdownPopupRef.current) {
+ const top = dropdownPopupRef.current.getBoundingClientRect().top;
+ dropdownPopupRef.current.style.maxHeight = `calc(100vh - ${top}px)`;
+ }
}
- }
- React.useEffect(resizeDropdownPopup, [showDropdown]);
- React.useEffect(() => {
- window.addEventListener("resize", resizeDropdownPopup);
- return () => {
- window.removeEventListener("resize", resizeDropdownPopup);
- };
- });
+ React.useEffect(resizeDropdownPopup, [showDropdown]);
+ React.useEffect(() => {
+ window.addEventListener("resize", resizeDropdownPopup);
+ return () => {
+ window.removeEventListener("resize", resizeDropdownPopup);
+ };
+ });
- return (
-
-
setShowDropdown(!showDropdown)}
- >
- {props.selectedIndex === undefined
- ? props.placeholderText
- : props.possibleOptions[props.selectedIndex]}
- expand_more
-
-
- {props.possibleOptions.map(mapFunc)}
-
-
- );
+ return (
+
+
setShowDropdown(!showDropdown)}
+ >
+ {props.selectedIndex === undefined
+ ? props.placeholderText
+ : props.possibleOptions[props.selectedIndex]}
+ expand_more
+
+
+ {props.possibleOptions.map(mapFunc)}
+
+
+ );
};
diff --git a/src/pages/operator/tsx/basic_components/DropdownInput.tsx b/src/pages/operator/tsx/basic_components/DropdownInput.tsx
index f2a84316..1fee073e 100644
--- a/src/pages/operator/tsx/basic_components/DropdownInput.tsx
+++ b/src/pages/operator/tsx/basic_components/DropdownInput.tsx
@@ -5,124 +5,127 @@ import e from "express";
import { text } from "stream/consumers";
export const DropdownInput = (props: {
- text: string;
- setText: (text: string) => void;
- selectedIndex?: number;
- setSelectedIndex: (index?: number) => void;
- possibleOptions: T[];
- placeholderText: string;
- placement: string;
- rows: number;
+ text: string;
+ setText: (text: string) => void;
+ selectedIndex?: number;
+ setSelectedIndex: (index?: number) => void;
+ possibleOptions: T[];
+ placeholderText: string;
+ placement: string;
+ rows: number;
}) => {
- const [showDropdown, setShowDropdown] = React.useState(false);
- const componentRef = React.useRef(null);
- const dropdownPopupRef = React.useRef(null);
+ const [showDropdown, setShowDropdown] = React.useState(false);
+ const componentRef = React.useRef(null);
+ const dropdownPopupRef = React.useRef(null);
- // Handler to close dropdown when click outside
- React.useEffect(() => {
- const handler = (e: any) => {
- if (componentRef.current && !componentRef.current.contains(e.target)) {
- setShowDropdown(false);
- }
- };
- if (showDropdown) {
- window.addEventListener("click", handler);
- return () => {
- window.removeEventListener("click", handler);
- };
+ // Handler to close dropdown when click outside
+ React.useEffect(() => {
+ const handler = (e: any) => {
+ if (
+ componentRef.current &&
+ !componentRef.current.contains(e.target)
+ ) {
+ setShowDropdown(false);
+ }
+ };
+ if (showDropdown) {
+ window.addEventListener("click", handler);
+ return () => {
+ window.removeEventListener("click", handler);
+ };
+ }
+ });
+
+ // Handler to update the selected index if the possible options or text changes
+ React.useEffect(() => {
+ let text = props.text.trim();
+ if (props.possibleOptions.includes(text as T)) {
+ props.setSelectedIndex(props.possibleOptions.indexOf(text as T));
+ } else {
+ props.setSelectedIndex(undefined);
+ }
+ }, [props.possibleOptions, props.text]);
+
+ // Function to convert each possible option into a button
+ function mapFunc(option: T, idx: number) {
+ const active = idx === props.selectedIndex;
+ return (
+ {
+ e.stopPropagation();
+ setShowDropdown(false);
+ props.setText(option as string);
+ if (!active) props.setSelectedIndex(idx);
+ }}
+ className={className("dropdown-input-option", { active })}
+ >
+ {option}
+
+ );
}
- });
- // Handler to update the selected index if the possible options or text changes
- React.useEffect(() => {
- let text = props.text.trim();
- if (props.possibleOptions.includes(text as T)) {
- props.setSelectedIndex(props.possibleOptions.indexOf(text as T));
- } else {
- props.setSelectedIndex(undefined);
+ // Set the max-height of the popup to the screen height minus the top of the popup
+ function resizeDropdownPopup() {
+ if (dropdownPopupRef.current) {
+ const top = dropdownPopupRef.current.getBoundingClientRect().top;
+ dropdownPopupRef.current.style.maxHeight = `calc(100vh - ${top}px)`;
+ }
}
- }, [props.possibleOptions, props.text]);
+ React.useEffect(resizeDropdownPopup, [showDropdown]);
+ React.useEffect(() => {
+ window.addEventListener("resize", resizeDropdownPopup);
+ return () => {
+ window.removeEventListener("resize", resizeDropdownPopup);
+ };
+ });
- // Function to convert each possible option into a button
- function mapFunc(option: T, idx: number) {
- const active = idx === props.selectedIndex;
return (
- {
- e.stopPropagation();
- setShowDropdown(false);
- props.setText(option as string);
- if (!active) props.setSelectedIndex(idx);
- }}
- className={className("dropdown-input-option", { active })}
- >
- {option}
-
+
);
- }
-
- // Set the max-height of the popup to the screen height minus the top of the popup
- function resizeDropdownPopup() {
- if (dropdownPopupRef.current) {
- const top = dropdownPopupRef.current.getBoundingClientRect().top;
- dropdownPopupRef.current.style.maxHeight = `calc(100vh - ${top}px)`;
- }
- }
- React.useEffect(resizeDropdownPopup, [showDropdown]);
- React.useEffect(() => {
- window.addEventListener("resize", resizeDropdownPopup);
- return () => {
- window.removeEventListener("resize", resizeDropdownPopup);
- };
- });
-
- return (
-
- );
};
diff --git a/src/pages/operator/tsx/basic_components/PopupModal.tsx b/src/pages/operator/tsx/basic_components/PopupModal.tsx
index eaadc323..def315db 100644
--- a/src/pages/operator/tsx/basic_components/PopupModal.tsx
+++ b/src/pages/operator/tsx/basic_components/PopupModal.tsx
@@ -4,26 +4,26 @@ import { className } from "shared/util";
/** Properties for {@link PopupModal} */
export type PopupModalProps = {
- /** If the popup should be shown */
- show: boolean;
- /**
- * Callback to set if the popup is shown, used to close the popup on
- * cancel, accept, or click outside of window.
- */
- setShow: (show: boolean) => void;
- /** Callback when the user clicks accept. */
- onAccept: () => void;
- /** Optional Callback when user cancels */
- onCancel?: () => void;
- /** Optional HTML id. */
- id?: string;
- /** Text to display on text button, defaults to "Accept" if undefined. */
- acceptButtonText?: string;
- /** Accept button disabled if true, enabled if false or undefined. */
- acceptDisabled?: boolean;
- /** Modal size */
- size?: "small" | "medium" | "large";
- mobile?: boolean;
+ /** If the popup should be shown */
+ show: boolean;
+ /**
+ * Callback to set if the popup is shown, used to close the popup on
+ * cancel, accept, or click outside of window.
+ */
+ setShow: (show: boolean) => void;
+ /** Callback when the user clicks accept. */
+ onAccept: () => void;
+ /** Optional Callback when user cancels */
+ onCancel?: () => void;
+ /** Optional HTML id. */
+ id?: string;
+ /** Text to display on text button, defaults to "Accept" if undefined. */
+ acceptButtonText?: string;
+ /** Accept button disabled if true, enabled if false or undefined. */
+ acceptDisabled?: boolean;
+ /** Modal size */
+ size?: "small" | "medium" | "large";
+ mobile?: boolean;
};
/**
@@ -32,60 +32,60 @@ export type PopupModalProps = {
* @param props see {@link PopupModalProps}
*/
export const PopupModal: React.FunctionComponent<
- React.PropsWithChildren
+ React.PropsWithChildren
> = (props) => {
- /** Call `onAccept` and hide the popup. */
- function handleClickAccept() {
- props.onAccept();
- props.setShow(false);
- }
- /** Handle user keyboard input. */
- function handleKeyDown(e: React.KeyboardEvent) {
- if (e.key == "Enter") {
- handleClickAccept();
- } else if (e.key == "Escape") {
- props.setShow(false);
+ /** Call `onAccept` and hide the popup. */
+ function handleClickAccept() {
+ props.onAccept();
+ props.setShow(false);
}
- }
- const size = props.size;
- const mobile = props.mobile;
- const element = props.show ? (
-
- {/* ) {
+ if (e.key == "Enter") {
+ handleClickAccept();
+ } else if (e.key == "Escape") {
+ props.setShow(false);
+ }
+ }
+ const size = props.size;
+ const mobile = props.mobile;
+ const element = props.show ? (
+
+ {/* */}
-
- {props.children}
-
- {
- props.setShow(false);
- if (props.onCancel) props.onCancel();
- }}
- >
- Cancel
-
-
- {props.acceptButtonText || "Accept"}
-
-
-
- {/* */}
-
-
- ) : null;
+
+ {props.children}
+
+ {
+ props.setShow(false);
+ if (props.onCancel) props.onCancel();
+ }}
+ >
+ Cancel
+
+
+ {props.acceptButtonText || "Accept"}
+
+
+
+ {/* */}
+
+
+ ) : null;
- return element;
+ return element;
};
diff --git a/src/pages/operator/tsx/basic_components/RadioGroup.tsx b/src/pages/operator/tsx/basic_components/RadioGroup.tsx
index 14731217..09e69f77 100644
--- a/src/pages/operator/tsx/basic_components/RadioGroup.tsx
+++ b/src/pages/operator/tsx/basic_components/RadioGroup.tsx
@@ -4,80 +4,82 @@ import "operator/css/RadioGroup.css";
import { isMobile } from "react-device-detect";
export const RadioButton = (props: {
- label: string;
- selected: boolean;
- onClick: () => void;
- functs: RadioFunctions;
+ label: string;
+ selected: boolean;
+ onClick: () => void;
+ functs: RadioFunctions;
}) => {
- return (
-
-
-
- {props.label}
-
-
- {props.functs.Edit && (
- mode_edit_outline
- )}
- {props.functs.Delete && (
- props.functs.Delete!(props.label)}
- >
- delete_outline
-
- )}
-
-
- );
+ return (
+
+
+
+ {props.label}
+
+
+ {props.functs.Edit && (
+
+ mode_edit_outline
+
+ )}
+ {props.functs.Delete && (
+ props.functs.Delete!(props.label)}
+ >
+ delete_outline
+
+ )}
+
+
+ );
};
export interface RadioFunctions {
- GetLabels: () => string[];
- SelectedLabel: (label: string) => void;
- // Add?: () => void
- Edit?: (label: string) => void;
- Delete?: (label: string) => void;
- // Start?: (label: string) => void,
- // Cancel?: () => void
+ GetLabels: () => string[];
+ SelectedLabel: (label: string) => void;
+ // Add?: () => void
+ Edit?: (label: string) => void;
+ Delete?: (label: string) => void;
+ // Start?: (label: string) => void,
+ // Cancel?: () => void
}
export const RadioGroup = (props: { functs: RadioFunctions }) => {
- const [selected, setSelected] = React.useState();
+ const [selected, setSelected] = React.useState();
- return (
- e.preventDefault()}
- >
- {props.functs.GetLabels().map((label, index) => (
-
{
- if (selected === label) {
- setSelected("");
- props.functs.SelectedLabel("");
- } else {
- setSelected(label);
- props.functs.SelectedLabel(label);
- }
- }}
- functs={props.functs}
- />
- ))}
- {/* {props.functs.Add &&
+ return (
+ e.preventDefault()}
+ >
+ {props.functs.GetLabels().map((label, index) => (
+ {
+ if (selected === label) {
+ setSelected("");
+ props.functs.SelectedLabel("");
+ } else {
+ setSelected(label);
+ props.functs.SelectedLabel(label);
+ }
+ }}
+ functs={props.functs}
+ />
+ ))}
+ {/* {props.functs.Add &&
add
@@ -87,6 +89,6 @@ export const RadioGroup = (props: { functs: RadioFunctions }) => {
play_arrow
} */}
-
- );
+
+ );
};
diff --git a/src/pages/operator/tsx/basic_components/TabGroup.tsx b/src/pages/operator/tsx/basic_components/TabGroup.tsx
index c5af3bbf..fad59f7f 100644
--- a/src/pages/operator/tsx/basic_components/TabGroup.tsx
+++ b/src/pages/operator/tsx/basic_components/TabGroup.tsx
@@ -3,62 +3,66 @@ import { className } from "shared/util";
import "operator/css/TabGroup.css";
export const Tab = (props: {
- label: string;
- active: boolean;
- onClick: () => void;
- pill: boolean;
+ label: string;
+ active: boolean;
+ onClick: () => void;
+ pill: boolean;
}) => {
- const active = props.active;
+ const active = props.active;
- return props.pill ? (
-
- {props.label}
-
- ) : (
-
- {props.label}
-
- );
+ return props.pill ? (
+
+ {props.label}
+
+ ) : (
+
+ {props.label}
+
+ );
};
export const TabGroup = (props: {
- tabLabels: string[];
- tabContent: ((active: boolean) => React.JSX.Element)[];
- startIdx: number;
- onChange: (index: number) => void;
- pill: boolean;
+ tabLabels: string[];
+ tabContent: ((active: boolean) => React.JSX.Element)[];
+ startIdx: number;
+ onChange: (index: number) => void;
+ pill: boolean;
}) => {
- const tabLabels = props.tabLabels;
- const tabContent = props.tabContent;
- const [activeIndex, setActiveIndex] = React.useState(props.startIdx);
+ const tabLabels = props.tabLabels;
+ const tabContent = props.tabContent;
+ const [activeIndex, setActiveIndex] = React.useState(
+ props.startIdx,
+ );
- return (
- e.preventDefault()}>
-
- {tabLabels.map((label, index) => (
- {
- props.onChange(index);
- setActiveIndex(index);
- }}
- pill={props.pill}
- />
- ))}
-
-
- {tabContent.map((renderFn, index) => renderFn(activeIndex === index))}
-
-
- );
+ return (
+ e.preventDefault()}>
+
+ {tabLabels.map((label, index) => (
+ {
+ props.onChange(index);
+ setActiveIndex(index);
+ }}
+ pill={props.pill}
+ />
+ ))}
+
+
+ {tabContent.map((renderFn, index) =>
+ renderFn(activeIndex === index),
+ )}
+
+
+ );
};
diff --git a/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx b/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx
index f95b7130..98acc593 100644
--- a/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx
+++ b/src/pages/operator/tsx/default_layouts/SIMPLE_LAYOUT.tsx
@@ -1,111 +1,111 @@
import {
- ComponentType,
- CameraViewId,
- ButtonPadId,
- CameraViewDefinition,
- ButtonPadDefinition,
- PanelDefinition,
- TabDefinition,
- LayoutDefinition,
- ActionMode,
- LayoutGridDefinition,
+ ComponentType,
+ CameraViewId,
+ ButtonPadId,
+ CameraViewDefinition,
+ ButtonPadDefinition,
+ PanelDefinition,
+ TabDefinition,
+ LayoutDefinition,
+ ActionMode,
+ LayoutGridDefinition,
} from "../utils/component_definitions";
/**
* Basic Layout
*/
export const BASIC_LAYOUT: LayoutDefinition = {
- type: ComponentType.Layout,
- displayMovementRecorder: false,
- displayTextToSpeech: false,
- displayLabels: true,
- actionMode: ActionMode.PressAndHold,
- children: [
- {
- type: ComponentType.LayoutGrid,
- children: [
+ type: ComponentType.Layout,
+ displayMovementRecorder: false,
+ displayTextToSpeech: false,
+ displayLabels: true,
+ actionMode: ActionMode.PressAndHold,
+ children: [
{
- type: ComponentType.Panel,
- children: [
- {
- type: ComponentType.SingleTab,
- label: "Camera Views",
- children: [
- // Overhead camera
+ type: ComponentType.LayoutGrid,
+ children: [
{
- type: ComponentType.CameraView,
- id: CameraViewId.overhead,
- displayButtons: true,
- children: [],
- } as CameraViewDefinition,
- {
- type: ComponentType.CameraView,
- id: CameraViewId.gripper,
- displayButtons: true,
- children: [],
- } as CameraViewDefinition,
- ],
- },
- ],
- } as PanelDefinition,
- ],
- } as LayoutGridDefinition,
- {
- type: ComponentType.LayoutGrid,
- children: [
- {
- type: ComponentType.Panel,
- children: [
- {
- type: ComponentType.SingleTab,
- label: "Base",
- children: [
- {
- type: ComponentType.ButtonPad,
- id: ButtonPadId.Base,
- } as ButtonPadDefinition,
- ],
- } as TabDefinition,
- {
- type: ComponentType.SingleTab,
- label: "Wrist & Gripper",
- children: [
- {
- type: ComponentType.ButtonPad,
- id: ButtonPadId.DexWrist,
- } as ButtonPadDefinition,
- ],
- } as TabDefinition,
- {
- type: ComponentType.SingleTab,
- label: "Arm & Lift",
- children: [
- {
- type: ComponentType.ButtonPad,
- id: ButtonPadId.Arm,
- } as ButtonPadDefinition,
- ],
- } as TabDefinition,
- ],
- } as PanelDefinition,
+ type: ComponentType.Panel,
+ children: [
+ {
+ type: ComponentType.SingleTab,
+ label: "Camera Views",
+ children: [
+ // Overhead camera
+ {
+ type: ComponentType.CameraView,
+ id: CameraViewId.overhead,
+ displayButtons: true,
+ children: [],
+ } as CameraViewDefinition,
+ {
+ type: ComponentType.CameraView,
+ id: CameraViewId.gripper,
+ displayButtons: true,
+ children: [],
+ } as CameraViewDefinition,
+ ],
+ },
+ ],
+ } as PanelDefinition,
+ ],
+ } as LayoutGridDefinition,
{
- type: ComponentType.Panel,
- children: [
- {
- type: ComponentType.SingleTab,
- label: "Safety",
- children: [
+ type: ComponentType.LayoutGrid,
+ children: [
{
- type: ComponentType.RunStopButton,
- },
+ type: ComponentType.Panel,
+ children: [
+ {
+ type: ComponentType.SingleTab,
+ label: "Base",
+ children: [
+ {
+ type: ComponentType.ButtonPad,
+ id: ButtonPadId.Base,
+ } as ButtonPadDefinition,
+ ],
+ } as TabDefinition,
+ {
+ type: ComponentType.SingleTab,
+ label: "Wrist & Gripper",
+ children: [
+ {
+ type: ComponentType.ButtonPad,
+ id: ButtonPadId.DexWrist,
+ } as ButtonPadDefinition,
+ ],
+ } as TabDefinition,
+ {
+ type: ComponentType.SingleTab,
+ label: "Arm & Lift",
+ children: [
+ {
+ type: ComponentType.ButtonPad,
+ id: ButtonPadId.Arm,
+ } as ButtonPadDefinition,
+ ],
+ } as TabDefinition,
+ ],
+ } as PanelDefinition,
{
- type: ComponentType.BatteryGuage,
- },
- ],
- },
- ],
- } as PanelDefinition,
- ],
- } as LayoutGridDefinition,
- ],
+ type: ComponentType.Panel,
+ children: [
+ {
+ type: ComponentType.SingleTab,
+ label: "Safety",
+ children: [
+ {
+ type: ComponentType.RunStopButton,
+ },
+ {
+ type: ComponentType.BatteryGuage,
+ },
+ ],
+ },
+ ],
+ } as PanelDefinition,
+ ],
+ } as LayoutGridDefinition,
+ ],
};
diff --git a/src/pages/operator/tsx/function_providers/BatteryVoltageFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/BatteryVoltageFunctionProvider.tsx
index 21e243c7..57a2157c 100644
--- a/src/pages/operator/tsx/function_providers/BatteryVoltageFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/BatteryVoltageFunctionProvider.tsx
@@ -1,58 +1,62 @@
import { FunctionProvider } from "./FunctionProvider";
export type BatteryVoltageFunctions = {
- getColor: () => string;
+ getColor: () => string;
};
export class BatteryVoltageFunctionProvider extends FunctionProvider {
- public voltage: number = 0.0;
- public voltageChangeCallback: (color: string) => void;
-
- constructor() {
- super();
- this.updateVoltage = this.updateVoltage.bind(this);
- }
-
- public updateVoltage(voltage: number): void {
- this.voltage = voltage;
- if (this.voltageChangeCallback) this.voltageChangeCallback(this.getColor());
- }
-
- private getColor() {
- let vbat_min = 10.0;
- let vbat_max = 12.0;
- let dv = (vbat_max - vbat_min) / 4.0;
-
- if (!this.voltage) return "red"; // throw 'Cannot retrieve battery voltage'
-
- if (this.voltage < vbat_min) {
- return "red"; // [64, 0, 0]
- } else if (this.voltage >= vbat_min && this.voltage < vbat_min + dv) {
- return "orange-red"; // [64, 32, 0]
- } else if (
- this.voltage >= vbat_min + dv &&
- this.voltage < vbat_min + 2 * dv
- ) {
- return "orange-yellow"; // [64, 64, 0]
- } else if (
- this.voltage >= vbat_min + 2 * dv &&
- this.voltage < vbat_min + 3 * dv
- ) {
- return "yellow"; // [64, 64, 0]
- } else if (this.voltage >= vbat_min + 3 * dv && this.voltage < vbat_max) {
- return "yellow-green"; // [32, 64, 0]
- } else {
- return "green"; // [0, 64, 0]
+ public voltage: number = 0.0;
+ public voltageChangeCallback: (color: string) => void;
+
+ constructor() {
+ super();
+ this.updateVoltage = this.updateVoltage.bind(this);
+ }
+
+ public updateVoltage(voltage: number): void {
+ this.voltage = voltage;
+ if (this.voltageChangeCallback)
+ this.voltageChangeCallback(this.getColor());
+ }
+
+ private getColor() {
+ let vbat_min = 10.0;
+ let vbat_max = 12.0;
+ let dv = (vbat_max - vbat_min) / 4.0;
+
+ if (!this.voltage) return "red"; // throw 'Cannot retrieve battery voltage'
+
+ if (this.voltage < vbat_min) {
+ return "red"; // [64, 0, 0]
+ } else if (this.voltage >= vbat_min && this.voltage < vbat_min + dv) {
+ return "orange-red"; // [64, 32, 0]
+ } else if (
+ this.voltage >= vbat_min + dv &&
+ this.voltage < vbat_min + 2 * dv
+ ) {
+ return "orange-yellow"; // [64, 64, 0]
+ } else if (
+ this.voltage >= vbat_min + 2 * dv &&
+ this.voltage < vbat_min + 3 * dv
+ ) {
+ return "yellow"; // [64, 64, 0]
+ } else if (
+ this.voltage >= vbat_min + 3 * dv &&
+ this.voltage < vbat_max
+ ) {
+ return "yellow-green"; // [32, 64, 0]
+ } else {
+ return "green"; // [0, 64, 0]
+ }
+ }
+
+ /**
+ * Records a callback from the function provider. The callback is called
+ * whenever the battery voltage changes.
+ *
+ * @param callback callback to function provider
+ */
+ public setVoltageChangeCallback(callback: (color: string) => void) {
+ this.voltageChangeCallback = callback;
}
- }
-
- /**
- * Records a callback from the function provider. The callback is called
- * whenever the battery voltage changes.
- *
- * @param callback callback to function provider
- */
- public setVoltageChangeCallback(callback: (color: string) => void) {
- this.voltageChangeCallback = callback;
- }
}
diff --git a/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx
index cd811b6d..369999cb 100644
--- a/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/ButtonFunctionProvider.tsx
@@ -1,8 +1,8 @@
import {
- JOINT_VELOCITIES,
- JOINT_INCREMENTS,
- ValidJoints,
- ValidJointStateDict,
+ JOINT_VELOCITIES,
+ JOINT_INCREMENTS,
+ ValidJoints,
+ ValidJointStateDict,
} from "shared/util";
import { ActionMode } from "../utils/component_definitions";
import { FunctionProvider } from "./FunctionProvider";
@@ -12,63 +12,63 @@ import { FunctionProvider } from "./FunctionProvider";
* the label of the button which appears in the tooltip.
*/
export enum ButtonPadButton {
- BaseForward = "Base Forward",
- BaseReverse = "Base Reverse",
- BaseRotateRight = "Base rotate right",
- BaseRotateLeft = "Base rotate left",
- ArmLift = "Arm lift",
- ArmLower = "Arm lower",
- ArmExtend = "Arm extend",
- ArmRetract = "Arm retract",
- GripperOpen = "Gripper open",
- GripperClose = "Gripper close",
- WristRotateIn = "Wrist rotate in",
- WristRotateOut = "Wrist rotate out",
- WristPitchUp = "Wrist pitch up",
- WristPitchDown = "Wrist pitch down",
- WristRollLeft = "Wrist roll left",
- WristRollRight = "Wrist roll right",
- CameraTiltUp = "Camera tilt up",
- CameraTiltDown = "Camera tilt down",
- CameraPanLeft = "Camera pan left",
- CameraPanRight = "Camera pan right",
+ BaseForward = "Base Forward",
+ BaseReverse = "Base Reverse",
+ BaseRotateRight = "Base rotate right",
+ BaseRotateLeft = "Base rotate left",
+ ArmLift = "Arm lift",
+ ArmLower = "Arm lower",
+ ArmExtend = "Arm extend",
+ ArmRetract = "Arm retract",
+ GripperOpen = "Gripper open",
+ GripperClose = "Gripper close",
+ WristRotateIn = "Wrist rotate in",
+ WristRotateOut = "Wrist rotate out",
+ WristPitchUp = "Wrist pitch up",
+ WristPitchDown = "Wrist pitch down",
+ WristRollLeft = "Wrist roll left",
+ WristRollRight = "Wrist roll right",
+ CameraTiltUp = "Camera tilt up",
+ CameraTiltDown = "Camera tilt down",
+ CameraPanLeft = "Camera pan left",
+ CameraPanRight = "Camera pan right",
}
/** Array of the pan tilt buttons */
export const panTiltButtons: ButtonPadButton[] = [
- ButtonPadButton.CameraTiltUp,
- ButtonPadButton.CameraTiltDown,
- ButtonPadButton.CameraPanLeft,
- ButtonPadButton.CameraPanRight,
+ ButtonPadButton.CameraTiltUp,
+ ButtonPadButton.CameraTiltDown,
+ ButtonPadButton.CameraPanLeft,
+ ButtonPadButton.CameraPanRight,
];
/** Button functions which require moving a joint in the negative direction. */
const negativeButtonPadFunctions = new Set([
- ButtonPadButton.BaseReverse,
- ButtonPadButton.BaseRotateRight,
- ButtonPadButton.ArmLower,
- ButtonPadButton.ArmRetract,
- ButtonPadButton.GripperClose,
- ButtonPadButton.WristRotateOut,
- ButtonPadButton.WristPitchDown,
- ButtonPadButton.WristRollLeft,
- ButtonPadButton.CameraTiltDown,
- ButtonPadButton.CameraPanRight,
+ ButtonPadButton.BaseReverse,
+ ButtonPadButton.BaseRotateRight,
+ ButtonPadButton.ArmLower,
+ ButtonPadButton.ArmRetract,
+ ButtonPadButton.GripperClose,
+ ButtonPadButton.WristRotateOut,
+ ButtonPadButton.WristPitchDown,
+ ButtonPadButton.WristRollLeft,
+ ButtonPadButton.CameraTiltDown,
+ ButtonPadButton.CameraPanRight,
]);
/** Functions called when the user interacts with a button. */
export type ButtonFunctions = {
- onClick: () => void;
- onRelease?: () => void;
- onLeave?: () => void;
+ onClick: () => void;
+ onRelease?: () => void;
+ onLeave?: () => void;
};
/** State for a single button on a button pad. */
export enum ButtonState {
- Inactive = "inactive",
- Active = "active",
- Collision = "collision",
- Limit = "limit",
+ Inactive = "inactive",
+ Active = "active",
+ Collision = "collision",
+ Limit = "limit",
}
/** Mapping from each type of button pad button to the state for that button */
@@ -78,293 +78,314 @@ export type ButtonStateMap = Map;
* 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.
- */
- private operatorCallback?: (buttonStateMap: ButtonStateMap) => void =
- undefined;
-
- constructor() {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- this.updateJointStates = this.updateJointStates.bind(this);
- this.setButtonActiveState = this.setButtonActiveState.bind(this);
- this.setButtonInactiveState = this.setButtonInactiveState.bind(this);
- }
-
- /**
- * Takes joint states and updates the button state map based on which joints
- * are in collision or at their limit.
- *
- * @param inJointLimit dictionary of joints whose limit booleans have changed
- * @param inCollision dictionary of joints whose collision booleans have changed
- */
- public updateJointStates(
- inJointLimit: ValidJointStateDict,
- inCollision: ValidJointStateDict,
- ) {
- // For all the joints that are in collision, set their corresponding buttons
- // either to collision (for the button corresponding to the direction the
- // collision is in) or inactive (for the button corresponding to the other direction).
- Object.keys(inCollision).forEach((k: string) => {
- const key = k as ValidJoints;
- const [inCollisionNeg, inCollisionPos] = inCollision[key]!;
- const buttons = getButtonsFromJointName(key);
- if (!buttons) return;
- let [buttonNeg, buttonPos] =
- key !== "joint_wrist_yaw" && key !== "joint_wrist_pitch"
- ? buttons
- : 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 prevInCollisionNeg = prevButtonStateNeg === ButtonState.Collision;
- const prevInCollisionPos = prevButtonStatePos === ButtonState.Collision;
- if (!prevButtonStateNeg || inCollisionNeg !== prevInCollisionNeg)
- this.buttonStateMap.set(
- buttonNeg,
- inCollisionNeg ? ButtonState.Collision : ButtonState.Inactive,
- );
- if (!prevButtonStatePos || inCollisionPos !== prevInCollisionPos)
- this.buttonStateMap.set(
- buttonPos,
- inCollisionPos ? ButtonState.Collision : ButtonState.Inactive,
- );
- });
-
- // For all the joints that are at their limit, set their corresponding buttons
- // either to limit (for the button corresponding to the direction the joint
- // limit is in) or inactive (for the button corresponding to the other direction).
- Object.keys(inJointLimit).forEach((k: string) => {
- const key = k as ValidJoints;
- const [inLimitNeg, inLimitPos] = inJointLimit[key]!;
- const buttons = getButtonsFromJointName(key);
- if (!buttons) return;
- const [buttonNeg, buttonPos] = buttons;
- const prevButtonStateNeg = this.buttonStateMap.get(buttonNeg);
- const prevButtonStatePos = this.buttonStateMap.get(buttonPos);
- const prevInLimitNeg = prevButtonStateNeg !== ButtonState.Limit;
- const prevInLimitPos = prevButtonStatePos !== ButtonState.Limit;
- if (prevButtonStateNeg == undefined || inLimitNeg !== prevInLimitNeg)
- this.buttonStateMap.set(
- buttonNeg,
- inLimitNeg ? ButtonState.Inactive : ButtonState.Limit,
- );
- if (prevButtonStatePos == undefined || inLimitPos !== prevInLimitPos)
- this.buttonStateMap.set(
- buttonPos,
- inLimitPos ? ButtonState.Inactive : ButtonState.Limit,
- );
- });
-
- if (this.operatorCallback) this.operatorCallback(this.buttonStateMap);
- }
-
- /**
- * Sets the local pointer to the operator's callback function, to be called
- * whenever the button state map updates.
- *
- * @param callback operator's callback function to update the button state map
- */
- public setOperatorCallback(
- callback: (buttonStateMap: ButtonStateMap) => void,
- ) {
- this.operatorCallback = callback;
- }
-
- /**
- * Sets a type of a button pad button to active.
- *
- * @param buttonType the button pad button to set active
- */
- private setButtonActiveState(buttonType: ButtonPadButton) {
- const currentState = this.buttonStateMap.get(buttonType);
-
- // Don't set to active if in collision or at it's limit
- if (
- currentState === ButtonState.Collision ||
- currentState === ButtonState.Limit
- )
- return;
-
- this.buttonStateMap.set(buttonType, ButtonState.Active);
- if (this.operatorCallback) this.operatorCallback(this.buttonStateMap);
- }
-
- /**
- * Sets a type of a button pad button to inactive.
- *
- * @param buttonType the button pad button to set active
- */
- private setButtonInactiveState(buttonType: ButtonPadButton) {
- const currentState = this.buttonStateMap.get(buttonType);
-
- // Don't set to inactive if in collision or at it's limit
- if (
- currentState === ButtonState.Collision ||
- currentState === ButtonState.Limit ||
- currentState === ButtonState.Inactive
- )
- return;
-
- this.buttonStateMap.set(buttonType, ButtonState.Inactive);
- if (this.operatorCallback) this.operatorCallback(this.buttonStateMap);
- }
-
- /**
- * Takes a ButtonPadFunction which indicates the type of button (e.g. drive
- * base forward, lift arm), and returns a set of functions to execute when
- * the user interacts with the button.
- *
- * @param buttonPadFunction the {@link ButtonPadButton}
- * @returns the {@link ButtonFunctions} for the button
- */
- public provideFunctions(buttonPadFunction: ButtonPadButton): ButtonFunctions {
- let action: () => void;
- const onLeave = () => {
- this.stopCurrentAction();
- this.setButtonInactiveState(buttonPadFunction);
- };
-
- const jointName: ValidJoints =
- getJointNameFromButtonFunction(buttonPadFunction);
- const multiplier: number = negativeButtonPadFunctions.has(buttonPadFunction)
- ? -1
- : 1;
- const velocity =
- multiplier *
- JOINT_VELOCITIES[jointName]! *
- FunctionProvider.velocityScale;
- const increment =
- multiplier *
- JOINT_INCREMENTS[jointName]! *
- FunctionProvider.velocityScale;
-
- switch (FunctionProvider.actionMode) {
- case ActionMode.StepActions:
- switch (buttonPadFunction) {
- case ButtonPadButton.BaseForward:
- case ButtonPadButton.BaseReverse:
- action = () => this.incrementalBaseDrive(velocity, 0.0);
- break;
- case ButtonPadButton.BaseRotateLeft:
- case ButtonPadButton.BaseRotateRight:
- action = () => this.incrementalBaseDrive(0.0, velocity);
- break;
- case ButtonPadButton.ArmLower:
- case ButtonPadButton.ArmLift:
- case ButtonPadButton.ArmExtend:
- case ButtonPadButton.ArmRetract:
- case ButtonPadButton.WristRotateIn:
- case ButtonPadButton.WristRotateOut:
- case ButtonPadButton.WristPitchUp:
- case ButtonPadButton.WristPitchDown:
- case ButtonPadButton.WristRollLeft:
- case ButtonPadButton.WristRollRight:
- case ButtonPadButton.GripperOpen:
- case ButtonPadButton.GripperClose:
- action = () => this.incrementalJointMovement(jointName, increment);
- break;
- case ButtonPadButton.CameraTiltUp:
- case ButtonPadButton.CameraTiltDown:
- case ButtonPadButton.CameraPanLeft:
- case ButtonPadButton.CameraPanRight:
- action = () => {
- this.incrementalJointMovement(jointName, increment);
- FunctionProvider.remoteRobot?.setToggle(
- "setFollowGripper",
- false,
- );
- };
- break;
- }
- return {
- onClick: () => {
- action();
- this.setButtonActiveState(buttonPadFunction);
- // Set button state inactive after 1 second
- setTimeout(
- () => this.setButtonInactiveState(buttonPadFunction),
- 1000,
- );
- },
- onLeave: onLeave,
+ 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.
+ */
+ private operatorCallback?: (buttonStateMap: ButtonStateMap) => void =
+ undefined;
+
+ constructor() {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ this.updateJointStates = this.updateJointStates.bind(this);
+ this.setButtonActiveState = this.setButtonActiveState.bind(this);
+ this.setButtonInactiveState = this.setButtonInactiveState.bind(this);
+ }
+
+ /**
+ * Takes joint states and updates the button state map based on which joints
+ * are in collision or at their limit.
+ *
+ * @param inJointLimit dictionary of joints whose limit booleans have changed
+ * @param inCollision dictionary of joints whose collision booleans have changed
+ */
+ public updateJointStates(
+ inJointLimit: ValidJointStateDict,
+ inCollision: ValidJointStateDict,
+ ) {
+ // For all the joints that are in collision, set their corresponding buttons
+ // either to collision (for the button corresponding to the direction the
+ // collision is in) or inactive (for the button corresponding to the other direction).
+ Object.keys(inCollision).forEach((k: string) => {
+ const key = k as ValidJoints;
+ const [inCollisionNeg, inCollisionPos] = inCollision[key]!;
+ const buttons = getButtonsFromJointName(key);
+ if (!buttons) return;
+ let [buttonNeg, buttonPos] =
+ key !== "joint_wrist_yaw" && key !== "joint_wrist_pitch"
+ ? buttons
+ : 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 prevInCollisionNeg =
+ prevButtonStateNeg === ButtonState.Collision;
+ const prevInCollisionPos =
+ prevButtonStatePos === ButtonState.Collision;
+ if (!prevButtonStateNeg || inCollisionNeg !== prevInCollisionNeg)
+ this.buttonStateMap.set(
+ buttonNeg,
+ inCollisionNeg
+ ? ButtonState.Collision
+ : ButtonState.Inactive,
+ );
+ if (!prevButtonStatePos || inCollisionPos !== prevInCollisionPos)
+ this.buttonStateMap.set(
+ buttonPos,
+ inCollisionPos
+ ? ButtonState.Collision
+ : ButtonState.Inactive,
+ );
+ });
+
+ // For all the joints that are at their limit, set their corresponding buttons
+ // either to limit (for the button corresponding to the direction the joint
+ // limit is in) or inactive (for the button corresponding to the other direction).
+ Object.keys(inJointLimit).forEach((k: string) => {
+ const key = k as ValidJoints;
+ const [inLimitNeg, inLimitPos] = inJointLimit[key]!;
+ const buttons = getButtonsFromJointName(key);
+ if (!buttons) return;
+ const [buttonNeg, buttonPos] = buttons;
+ const prevButtonStateNeg = this.buttonStateMap.get(buttonNeg);
+ const prevButtonStatePos = this.buttonStateMap.get(buttonPos);
+ const prevInLimitNeg = prevButtonStateNeg !== ButtonState.Limit;
+ const prevInLimitPos = prevButtonStatePos !== ButtonState.Limit;
+ if (
+ prevButtonStateNeg == undefined ||
+ inLimitNeg !== prevInLimitNeg
+ )
+ this.buttonStateMap.set(
+ buttonNeg,
+ inLimitNeg ? ButtonState.Inactive : ButtonState.Limit,
+ );
+ if (
+ prevButtonStatePos == undefined ||
+ inLimitPos !== prevInLimitPos
+ )
+ this.buttonStateMap.set(
+ buttonPos,
+ inLimitPos ? ButtonState.Inactive : ButtonState.Limit,
+ );
+ });
+
+ if (this.operatorCallback) this.operatorCallback(this.buttonStateMap);
+ }
+
+ /**
+ * Sets the local pointer to the operator's callback function, to be called
+ * whenever the button state map updates.
+ *
+ * @param callback operator's callback function to update the button state map
+ */
+ public setOperatorCallback(
+ callback: (buttonStateMap: ButtonStateMap) => void,
+ ) {
+ this.operatorCallback = callback;
+ }
+
+ /**
+ * Sets a type of a button pad button to active.
+ *
+ * @param buttonType the button pad button to set active
+ */
+ private setButtonActiveState(buttonType: ButtonPadButton) {
+ const currentState = this.buttonStateMap.get(buttonType);
+
+ // Don't set to active if in collision or at it's limit
+ if (
+ currentState === ButtonState.Collision ||
+ currentState === ButtonState.Limit
+ )
+ return;
+
+ this.buttonStateMap.set(buttonType, ButtonState.Active);
+ if (this.operatorCallback) this.operatorCallback(this.buttonStateMap);
+ }
+
+ /**
+ * Sets a type of a button pad button to inactive.
+ *
+ * @param buttonType the button pad button to set active
+ */
+ private setButtonInactiveState(buttonType: ButtonPadButton) {
+ const currentState = this.buttonStateMap.get(buttonType);
+
+ // Don't set to inactive if in collision or at it's limit
+ if (
+ currentState === ButtonState.Collision ||
+ currentState === ButtonState.Limit ||
+ currentState === ButtonState.Inactive
+ )
+ return;
+
+ this.buttonStateMap.set(buttonType, ButtonState.Inactive);
+ if (this.operatorCallback) this.operatorCallback(this.buttonStateMap);
+ }
+
+ /**
+ * Takes a ButtonPadFunction which indicates the type of button (e.g. drive
+ * base forward, lift arm), and returns a set of functions to execute when
+ * the user interacts with the button.
+ *
+ * @param buttonPadFunction the {@link ButtonPadButton}
+ * @returns the {@link ButtonFunctions} for the button
+ */
+ public provideFunctions(
+ buttonPadFunction: ButtonPadButton,
+ ): ButtonFunctions {
+ let action: () => void;
+ const onLeave = () => {
+ this.stopCurrentAction();
+ this.setButtonInactiveState(buttonPadFunction);
};
- case ActionMode.PressAndHold:
- case ActionMode.ClickClick:
- switch (buttonPadFunction) {
- case ButtonPadButton.BaseForward:
- case ButtonPadButton.BaseReverse:
- action = () => this.continuousBaseDrive(velocity, 0.0);
- break;
- case ButtonPadButton.BaseRotateLeft:
- case ButtonPadButton.BaseRotateRight:
- action = () => this.continuousBaseDrive(0.0, velocity);
- break;
-
- case ButtonPadButton.ArmLower:
- case ButtonPadButton.ArmLift:
- case ButtonPadButton.ArmExtend:
- case ButtonPadButton.ArmRetract:
- case ButtonPadButton.WristRotateIn:
- case ButtonPadButton.WristRotateOut:
- case ButtonPadButton.WristPitchUp:
- case ButtonPadButton.WristPitchDown:
- case ButtonPadButton.WristRollLeft:
- case ButtonPadButton.WristRollRight:
- case ButtonPadButton.GripperOpen:
- case ButtonPadButton.GripperClose:
- action = () => this.continuousJointMovement(jointName, increment);
- break;
- case ButtonPadButton.CameraTiltUp:
- case ButtonPadButton.CameraTiltDown:
- case ButtonPadButton.CameraPanLeft:
- case ButtonPadButton.CameraPanRight:
- action = () => {
- this.continuousJointMovement(jointName, increment);
- FunctionProvider.remoteRobot?.setToggle(
- "setFollowGripper",
- false,
- );
- };
- break;
- }
- return FunctionProvider.actionMode === ActionMode.PressAndHold
- ? {
- onClick: () => {
- action();
- this.setButtonActiveState(buttonPadFunction);
- },
- // For press-release, stop when button released
- onRelease: () => {
- this.stopCurrentAction();
- this.setButtonInactiveState(buttonPadFunction);
- },
- onLeave: onLeave,
- }
- : {
- // For click-click, stop if button already active
- onClick: () => {
- if (this.activeVelocityAction) {
- this.stopCurrentAction();
- this.setButtonInactiveState(buttonPadFunction);
- } else {
- action();
- this.setButtonActiveState(buttonPadFunction);
+ const jointName: ValidJoints =
+ getJointNameFromButtonFunction(buttonPadFunction);
+ const multiplier: number = negativeButtonPadFunctions.has(
+ buttonPadFunction,
+ )
+ ? -1
+ : 1;
+ const velocity =
+ multiplier *
+ JOINT_VELOCITIES[jointName]! *
+ FunctionProvider.velocityScale;
+ const increment =
+ multiplier *
+ JOINT_INCREMENTS[jointName]! *
+ FunctionProvider.velocityScale;
+
+ switch (FunctionProvider.actionMode) {
+ case ActionMode.StepActions:
+ switch (buttonPadFunction) {
+ case ButtonPadButton.BaseForward:
+ case ButtonPadButton.BaseReverse:
+ action = () => this.incrementalBaseDrive(velocity, 0.0);
+ break;
+ case ButtonPadButton.BaseRotateLeft:
+ case ButtonPadButton.BaseRotateRight:
+ action = () => this.incrementalBaseDrive(0.0, velocity);
+ break;
+ case ButtonPadButton.ArmLower:
+ case ButtonPadButton.ArmLift:
+ case ButtonPadButton.ArmExtend:
+ case ButtonPadButton.ArmRetract:
+ case ButtonPadButton.WristRotateIn:
+ case ButtonPadButton.WristRotateOut:
+ case ButtonPadButton.WristPitchUp:
+ case ButtonPadButton.WristPitchDown:
+ case ButtonPadButton.WristRollLeft:
+ case ButtonPadButton.WristRollRight:
+ case ButtonPadButton.GripperOpen:
+ case ButtonPadButton.GripperClose:
+ action = () =>
+ this.incrementalJointMovement(jointName, increment);
+ break;
+ case ButtonPadButton.CameraTiltUp:
+ case ButtonPadButton.CameraTiltDown:
+ case ButtonPadButton.CameraPanLeft:
+ case ButtonPadButton.CameraPanRight:
+ action = () => {
+ this.incrementalJointMovement(jointName, increment);
+ FunctionProvider.remoteRobot?.setToggle(
+ "setFollowGripper",
+ false,
+ );
+ };
+ break;
+ }
+ return {
+ onClick: () => {
+ action();
+ this.setButtonActiveState(buttonPadFunction);
+ // Set button state inactive after 1 second
+ setTimeout(
+ () =>
+ this.setButtonInactiveState(buttonPadFunction),
+ 1000,
+ );
+ },
+ onLeave: onLeave,
+ };
+ case ActionMode.PressAndHold:
+ case ActionMode.ClickClick:
+ switch (buttonPadFunction) {
+ case ButtonPadButton.BaseForward:
+ case ButtonPadButton.BaseReverse:
+ action = () => this.continuousBaseDrive(velocity, 0.0);
+ break;
+ case ButtonPadButton.BaseRotateLeft:
+ case ButtonPadButton.BaseRotateRight:
+ action = () => this.continuousBaseDrive(0.0, velocity);
+ break;
+
+ case ButtonPadButton.ArmLower:
+ case ButtonPadButton.ArmLift:
+ case ButtonPadButton.ArmExtend:
+ case ButtonPadButton.ArmRetract:
+ case ButtonPadButton.WristRotateIn:
+ case ButtonPadButton.WristRotateOut:
+ case ButtonPadButton.WristPitchUp:
+ case ButtonPadButton.WristPitchDown:
+ case ButtonPadButton.WristRollLeft:
+ case ButtonPadButton.WristRollRight:
+ case ButtonPadButton.GripperOpen:
+ case ButtonPadButton.GripperClose:
+ action = () =>
+ this.continuousJointMovement(jointName, increment);
+ break;
+ case ButtonPadButton.CameraTiltUp:
+ case ButtonPadButton.CameraTiltDown:
+ case ButtonPadButton.CameraPanLeft:
+ case ButtonPadButton.CameraPanRight:
+ action = () => {
+ this.continuousJointMovement(jointName, increment);
+ FunctionProvider.remoteRobot?.setToggle(
+ "setFollowGripper",
+ false,
+ );
+ };
+ break;
}
- },
- onLeave: onLeave,
- };
+
+ return FunctionProvider.actionMode === ActionMode.PressAndHold
+ ? {
+ onClick: () => {
+ action();
+ this.setButtonActiveState(buttonPadFunction);
+ },
+ // For press-release, stop when button released
+ onRelease: () => {
+ this.stopCurrentAction();
+ this.setButtonInactiveState(buttonPadFunction);
+ },
+ onLeave: onLeave,
+ }
+ : {
+ // For click-click, stop if button already active
+ onClick: () => {
+ if (this.activeVelocityAction) {
+ this.stopCurrentAction();
+ this.setButtonInactiveState(
+ buttonPadFunction,
+ );
+ } else {
+ action();
+ this.setButtonActiveState(buttonPadFunction);
+ }
+ },
+ onLeave: onLeave,
+ };
+ }
}
- }
}
/**
@@ -375,32 +396,50 @@ export class ButtonFunctionProvider extends FunctionProvider {
* negative or positive direction respectively)
*/
function getButtonsFromJointName(
- jointName: ValidJoints,
+ jointName: ValidJoints,
): [ButtonPadButton, ButtonPadButton] | undefined {
- switch (jointName) {
- case "joint_gripper_finger_left":
- return [ButtonPadButton.GripperClose, ButtonPadButton.GripperOpen];
- case "wrist_extension":
- return [ButtonPadButton.ArmRetract, ButtonPadButton.ArmExtend];
- case "joint_lift":
- return [ButtonPadButton.ArmLower, ButtonPadButton.ArmLift];
- case "joint_wrist_roll":
- return [ButtonPadButton.WristRollLeft, ButtonPadButton.WristRollRight];
- case "joint_wrist_pitch":
- return [ButtonPadButton.WristPitchDown, ButtonPadButton.WristPitchUp];
- case "joint_wrist_yaw":
- return [ButtonPadButton.WristRotateOut, ButtonPadButton.WristRotateIn];
- case "translate_mobile_base":
- return [ButtonPadButton.BaseForward, ButtonPadButton.BaseReverse];
- case "rotate_mobile_base":
- return [ButtonPadButton.BaseRotateLeft, ButtonPadButton.BaseRotateRight];
- case "joint_head_pan":
- return [ButtonPadButton.CameraPanRight, ButtonPadButton.CameraPanLeft];
- case "joint_head_tilt":
- return [ButtonPadButton.CameraTiltDown, ButtonPadButton.CameraTiltUp];
- default:
- return undefined;
- }
+ switch (jointName) {
+ case "joint_gripper_finger_left":
+ return [ButtonPadButton.GripperClose, ButtonPadButton.GripperOpen];
+ case "wrist_extension":
+ return [ButtonPadButton.ArmRetract, ButtonPadButton.ArmExtend];
+ case "joint_lift":
+ return [ButtonPadButton.ArmLower, ButtonPadButton.ArmLift];
+ case "joint_wrist_roll":
+ return [
+ ButtonPadButton.WristRollLeft,
+ ButtonPadButton.WristRollRight,
+ ];
+ case "joint_wrist_pitch":
+ return [
+ ButtonPadButton.WristPitchDown,
+ ButtonPadButton.WristPitchUp,
+ ];
+ case "joint_wrist_yaw":
+ return [
+ ButtonPadButton.WristRotateOut,
+ ButtonPadButton.WristRotateIn,
+ ];
+ case "translate_mobile_base":
+ return [ButtonPadButton.BaseForward, ButtonPadButton.BaseReverse];
+ case "rotate_mobile_base":
+ return [
+ ButtonPadButton.BaseRotateLeft,
+ ButtonPadButton.BaseRotateRight,
+ ];
+ case "joint_head_pan":
+ return [
+ ButtonPadButton.CameraPanRight,
+ ButtonPadButton.CameraPanLeft,
+ ];
+ case "joint_head_tilt":
+ return [
+ ButtonPadButton.CameraTiltDown,
+ ButtonPadButton.CameraTiltUp,
+ ];
+ default:
+ return undefined;
+ }
}
/**
@@ -410,50 +449,50 @@ function getButtonsFromJointName(
* @returns the name of the corresponding joint
*/
function getJointNameFromButtonFunction(
- buttonType: ButtonPadButton,
+ buttonType: ButtonPadButton,
): ValidJoints {
- switch (buttonType) {
- case ButtonPadButton.BaseReverse:
- case ButtonPadButton.BaseForward:
- return "translate_mobile_base";
-
- case ButtonPadButton.BaseRotateLeft:
- case ButtonPadButton.BaseRotateRight:
- return "rotate_mobile_base";
-
- case ButtonPadButton.ArmLower:
- case ButtonPadButton.ArmLift:
- return "joint_lift";
-
- case ButtonPadButton.ArmRetract:
- case ButtonPadButton.ArmExtend:
- return "wrist_extension";
-
- case ButtonPadButton.GripperClose:
- case ButtonPadButton.GripperOpen:
- return "joint_gripper_finger_left";
-
- case ButtonPadButton.WristRollLeft:
- case ButtonPadButton.WristRollRight:
- return "joint_wrist_roll";
-
- case ButtonPadButton.WristPitchUp:
- case ButtonPadButton.WristPitchDown:
- return "joint_wrist_pitch";
-
- case ButtonPadButton.WristRotateIn:
- case ButtonPadButton.WristRotateOut:
- return "joint_wrist_yaw";
-
- case ButtonPadButton.CameraTiltUp:
- case ButtonPadButton.CameraTiltDown:
- return "joint_head_tilt";
-
- case ButtonPadButton.CameraPanLeft:
- case ButtonPadButton.CameraPanRight:
- return "joint_head_pan";
-
- default:
- throw Error("unknown button pad function" + buttonType);
- }
+ switch (buttonType) {
+ case ButtonPadButton.BaseReverse:
+ case ButtonPadButton.BaseForward:
+ return "translate_mobile_base";
+
+ case ButtonPadButton.BaseRotateLeft:
+ case ButtonPadButton.BaseRotateRight:
+ return "rotate_mobile_base";
+
+ case ButtonPadButton.ArmLower:
+ case ButtonPadButton.ArmLift:
+ return "joint_lift";
+
+ case ButtonPadButton.ArmRetract:
+ case ButtonPadButton.ArmExtend:
+ return "wrist_extension";
+
+ case ButtonPadButton.GripperClose:
+ case ButtonPadButton.GripperOpen:
+ return "joint_gripper_finger_left";
+
+ case ButtonPadButton.WristRollLeft:
+ case ButtonPadButton.WristRollRight:
+ return "joint_wrist_roll";
+
+ case ButtonPadButton.WristPitchUp:
+ case ButtonPadButton.WristPitchDown:
+ return "joint_wrist_pitch";
+
+ case ButtonPadButton.WristRotateIn:
+ case ButtonPadButton.WristRotateOut:
+ return "joint_wrist_yaw";
+
+ case ButtonPadButton.CameraTiltUp:
+ case ButtonPadButton.CameraTiltDown:
+ return "joint_head_tilt";
+
+ case ButtonPadButton.CameraPanLeft:
+ case ButtonPadButton.CameraPanRight:
+ return "joint_head_pan";
+
+ default:
+ throw Error("unknown button pad function" + buttonType);
+ }
}
diff --git a/src/pages/operator/tsx/function_providers/FunctionProvider.tsx b/src/pages/operator/tsx/function_providers/FunctionProvider.tsx
index c1678726..b1bff6ac 100644
--- a/src/pages/operator/tsx/function_providers/FunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/FunctionProvider.tsx
@@ -8,90 +8,87 @@ import { ActionMode } from "../utils/component_definitions";
* interface
*/
export abstract class FunctionProvider {
- protected static remoteRobot?: RemoteRobot;
- public static velocityScale: number;
- public static actionMode: ActionMode;
- public activeVelocityAction?: VelocityCommand;
- public velocityExecutionHeartbeat?: number; // ReturnType
+ protected static remoteRobot?: RemoteRobot;
+ public static velocityScale: number;
+ public static actionMode: ActionMode;
+ public activeVelocityAction?: VelocityCommand;
+ public velocityExecutionHeartbeat?: number; // ReturnType
- /**
- * Adds a remote robot instance to this function provider. This must be called
- * before any components of the interface will be able to execute functions
- * to change the state of the robot.
- *
- * @param remoteRobot the remote robot instance to add
- */
- static addRemoteRobot(remoteRobot: RemoteRobot) {
- FunctionProvider.remoteRobot = remoteRobot;
- }
-
- /**
- * Sets the initial values for the velocity scale and action mode
- *
- * @param velocityScale initial velocity scale
- * @param actionMode initial action mode
- */
- static initialize(velocityScale: number, actionMode: ActionMode) {
- this.velocityScale = velocityScale;
- this.actionMode = actionMode;
- }
+ /**
+ * Adds a remote robot instance to this function provider. This must be called
+ * before any components of the interface will be able to execute functions
+ * to change the state of the robot.
+ *
+ * @param remoteRobot the remote robot instance to add
+ */
+ static addRemoteRobot(remoteRobot: RemoteRobot) {
+ FunctionProvider.remoteRobot = remoteRobot;
+ }
- public incrementalBaseDrive(linVel: number, angVel: number) {
- this.stopCurrentAction();
- this.activeVelocityAction = FunctionProvider.remoteRobot?.driveBase(
- linVel,
- angVel,
- );
- }
+ /**
+ * Sets the initial values for the velocity scale and action mode
+ *
+ * @param velocityScale initial velocity scale
+ * @param actionMode initial action mode
+ */
+ static initialize(velocityScale: number, actionMode: ActionMode) {
+ this.velocityScale = velocityScale;
+ this.actionMode = actionMode;
+ }
- public incrementalJointMovement(jointName: ValidJoints, increment: number) {
- this.stopCurrentAction();
- this.activeVelocityAction = FunctionProvider.remoteRobot?.incrementalMove(
- jointName,
- increment,
- );
- }
+ public incrementalBaseDrive(linVel: number, angVel: number) {
+ this.stopCurrentAction();
+ this.activeVelocityAction = FunctionProvider.remoteRobot?.driveBase(
+ linVel,
+ angVel,
+ );
+ }
- public continuousBaseDrive(linVel: number, angVel: number) {
- this.stopCurrentAction();
- this.activeVelocityAction = FunctionProvider.remoteRobot?.driveBase(
- linVel,
- angVel,
- );
- this.velocityExecutionHeartbeat = window.setInterval(() => {
- this.activeVelocityAction = FunctionProvider.remoteRobot?.driveBase(
- linVel,
- angVel,
- );
- }, 150);
- }
+ public incrementalJointMovement(jointName: ValidJoints, increment: number) {
+ this.stopCurrentAction();
+ this.activeVelocityAction =
+ FunctionProvider.remoteRobot?.incrementalMove(jointName, increment);
+ }
- public continuousJointMovement(jointName: ValidJoints, increment: number) {
- this.stopCurrentAction();
- this.activeVelocityAction = FunctionProvider.remoteRobot?.incrementalMove(
- jointName,
- increment,
- );
- this.velocityExecutionHeartbeat = window.setInterval(() => {
- this.activeVelocityAction = FunctionProvider.remoteRobot?.incrementalMove(
- jointName,
- increment,
- );
- }, 150);
- }
+ public continuousBaseDrive(linVel: number, angVel: number) {
+ this.stopCurrentAction();
+ this.activeVelocityAction = FunctionProvider.remoteRobot?.driveBase(
+ linVel,
+ angVel,
+ );
+ this.velocityExecutionHeartbeat = window.setInterval(() => {
+ this.activeVelocityAction = FunctionProvider.remoteRobot?.driveBase(
+ linVel,
+ angVel,
+ );
+ }, 150);
+ }
- public stopCurrentAction(send_stop_command: boolean = false) {
- if (send_stop_command) FunctionProvider.remoteRobot?.stopTrajectory();
- if (this.activeVelocityAction) {
- // TODO: this.activeVelocityAction.stop sometimes (always?) executes the
- // exact same cancellation command(s) as FunctionProvider.remoteRobot?.stopTrajectory,
- // which means we are unnecessarily calling it twice.
- if (send_stop_command) this.activeVelocityAction.stop();
- this.activeVelocityAction = undefined;
+ public continuousJointMovement(jointName: ValidJoints, increment: number) {
+ this.stopCurrentAction();
+ this.activeVelocityAction =
+ FunctionProvider.remoteRobot?.incrementalMove(jointName, increment);
+ this.velocityExecutionHeartbeat = window.setInterval(() => {
+ this.activeVelocityAction =
+ FunctionProvider.remoteRobot?.incrementalMove(
+ jointName,
+ increment,
+ );
+ }, 150);
}
- if (this.velocityExecutionHeartbeat) {
- clearInterval(this.velocityExecutionHeartbeat);
- this.velocityExecutionHeartbeat = undefined;
+
+ public stopCurrentAction(send_stop_command: boolean = false) {
+ if (send_stop_command) FunctionProvider.remoteRobot?.stopTrajectory();
+ if (this.activeVelocityAction) {
+ // TODO: this.activeVelocityAction.stop sometimes (always?) executes the
+ // exact same cancellation command(s) as FunctionProvider.remoteRobot?.stopTrajectory,
+ // which means we are unnecessarily calling it twice.
+ if (send_stop_command) this.activeVelocityAction.stop();
+ this.activeVelocityAction = undefined;
+ }
+ if (this.velocityExecutionHeartbeat) {
+ clearInterval(this.velocityExecutionHeartbeat);
+ this.velocityExecutionHeartbeat = undefined;
+ }
}
- }
}
diff --git a/src/pages/operator/tsx/function_providers/MapFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/MapFunctionProvider.tsx
index d73ac148..a59ba534 100644
--- a/src/pages/operator/tsx/function_providers/MapFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/MapFunctionProvider.tsx
@@ -4,34 +4,35 @@ import { FunctionProvider } from "./FunctionProvider";
import { occupancyGrid } from "operator/tsx/index";
export class MapFunctionProvider extends FunctionProvider {
- constructor() {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- FunctionProvider.remoteRobot?.getOccupancyGrid("getOccupancyGrid");
- }
+ constructor() {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ FunctionProvider.remoteRobot?.getOccupancyGrid("getOccupancyGrid");
+ }
- public provideFunctions(mapFunction: MapFunction) {
- switch (mapFunction) {
- case MapFunction.GetMap:
- return occupancyGrid;
- case MapFunction.GetPose:
- return () => {
- return FunctionProvider.remoteRobot?.getMapPose();
- };
- case MapFunction.MoveBase:
- return (pose: ROSPose) => {
- // FunctionProvider.remoteRobot?.stopExecution()
- FunctionProvider.remoteRobot?.moveBase(pose);
- };
- case MapFunction.GoalReached:
- return () => {
- let goalReached = FunctionProvider.remoteRobot?.isGoalReached();
- if (goalReached) {
- FunctionProvider.remoteRobot?.setGoalReached(false);
- return true;
- }
- return false;
- };
+ public provideFunctions(mapFunction: MapFunction) {
+ switch (mapFunction) {
+ case MapFunction.GetMap:
+ return occupancyGrid;
+ case MapFunction.GetPose:
+ return () => {
+ return FunctionProvider.remoteRobot?.getMapPose();
+ };
+ case MapFunction.MoveBase:
+ return (pose: ROSPose) => {
+ // FunctionProvider.remoteRobot?.stopExecution()
+ FunctionProvider.remoteRobot?.moveBase(pose);
+ };
+ case MapFunction.GoalReached:
+ return () => {
+ let goalReached =
+ FunctionProvider.remoteRobot?.isGoalReached();
+ if (goalReached) {
+ FunctionProvider.remoteRobot?.setGoalReached(false);
+ return true;
+ }
+ return false;
+ };
+ }
}
- }
}
diff --git a/src/pages/operator/tsx/function_providers/MovementRecorderFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/MovementRecorderFunctionProvider.tsx
index e5a2dc8e..14eca06c 100644
--- a/src/pages/operator/tsx/function_providers/MovementRecorderFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/MovementRecorderFunctionProvider.tsx
@@ -1,107 +1,112 @@
import { FunctionProvider } from "./FunctionProvider";
import {
- MovementRecorderFunctions,
- MovementRecorderFunction,
+ MovementRecorderFunctions,
+ MovementRecorderFunction,
} from "../layout_components/MovementRecorder";
import { RobotPose, ValidJoints } from "shared/util";
import { StorageHandler } from "../storage_handler/StorageHandler";
export class MovementRecorderFunctionProvider extends FunctionProvider {
- private recordPosesHeartbeat?: number; // ReturnType
- private poses: RobotPose[];
- private storageHandler: StorageHandler;
+ private recordPosesHeartbeat?: number; // ReturnType
+ private poses: RobotPose[];
+ private storageHandler: StorageHandler;
- constructor(storageHandler: StorageHandler) {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- this.poses = [];
- this.storageHandler = storageHandler;
- }
+ constructor(storageHandler: StorageHandler) {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ this.poses = [];
+ this.storageHandler = storageHandler;
+ }
- public provideFunctions(poseRecordFunction: MovementRecorderFunction) {
- switch (poseRecordFunction) {
- case MovementRecorderFunction.Record:
- return () => {
- let lastJoint: ValidJoints | undefined;
- this.recordPosesHeartbeat = window.setInterval(() => {
- const currentPose: RobotPose =
- FunctionProvider.remoteRobot!.sensors.getRobotPose(
- true,
- true,
- true,
- );
- const lastPose =
- this.poses.length == 0
- ? undefined
- : this.poses[this.poses.length - 1];
- if (lastPose) {
- Object.keys(currentPose).map((key, index) => {
- if (
- Math.abs(
- currentPose[key as ValidJoints]! -
- lastPose[key as ValidJoints]!,
- ) > 0.025
- ) {
- if (!lastJoint || lastJoint != key) {
- lastJoint = key as ValidJoints;
- this.poses.push(currentPose);
- return;
- } else {
- this.poses[this.poses.length - 1][lastJoint] =
- currentPose[key as ValidJoints];
- }
- }
- });
- } else {
- this.poses.push(currentPose);
- }
- }, 50);
- };
- case MovementRecorderFunction.SaveRecording:
- return (name: string) => {
- if (this.recordPosesHeartbeat) {
- clearInterval(this.recordPosesHeartbeat);
- this.recordPosesHeartbeat = undefined;
- }
- this.storageHandler.savePoseRecording(name, this.poses);
- this.poses = [];
- };
- case MovementRecorderFunction.StopRecording:
- return () => {
- if (this.recordPosesHeartbeat) {
- clearInterval(this.recordPosesHeartbeat);
- this.recordPosesHeartbeat = undefined;
- }
- this.poses = [];
- };
- case MovementRecorderFunction.SavedRecordingNames:
- return () => {
- return this.storageHandler.getRecordingNames();
- };
- case MovementRecorderFunction.DeleteRecording:
- return (recordingID: number) => {
- let recordingNames = this.storageHandler.getRecordingNames();
- this.storageHandler.deleteRecording(recordingNames[recordingID]);
- };
- case MovementRecorderFunction.DeleteRecordingName:
- return (name: string) => {
- this.storageHandler.deleteRecording(name);
- };
- case MovementRecorderFunction.LoadRecording:
- return (recordingID: number) => {
- let recordingNames = this.storageHandler.getRecordingNames();
- let recording = this.storageHandler.getRecording(
- recordingNames[recordingID],
- );
- FunctionProvider.remoteRobot?.playbackPoses(recording);
- };
- case MovementRecorderFunction.LoadRecordingName:
- return (name: string) => {
- let recording = this.storageHandler.getRecording(name);
- FunctionProvider.remoteRobot?.playbackPoses(recording);
- };
- case MovementRecorderFunction.Cancel:
- return () => FunctionProvider.remoteRobot?.stopTrajectory();
+ public provideFunctions(poseRecordFunction: MovementRecorderFunction) {
+ switch (poseRecordFunction) {
+ case MovementRecorderFunction.Record:
+ return () => {
+ let lastJoint: ValidJoints | undefined;
+ this.recordPosesHeartbeat = window.setInterval(() => {
+ const currentPose: RobotPose =
+ FunctionProvider.remoteRobot!.sensors.getRobotPose(
+ true,
+ true,
+ true,
+ );
+ const lastPose =
+ this.poses.length == 0
+ ? undefined
+ : this.poses[this.poses.length - 1];
+ if (lastPose) {
+ Object.keys(currentPose).map((key, index) => {
+ if (
+ Math.abs(
+ currentPose[key as ValidJoints]! -
+ lastPose[key as ValidJoints]!,
+ ) > 0.025
+ ) {
+ if (!lastJoint || lastJoint != key) {
+ lastJoint = key as ValidJoints;
+ this.poses.push(currentPose);
+ return;
+ } else {
+ this.poses[this.poses.length - 1][
+ lastJoint
+ ] = currentPose[key as ValidJoints];
+ }
+ }
+ });
+ } else {
+ this.poses.push(currentPose);
+ }
+ }, 50);
+ };
+ case MovementRecorderFunction.SaveRecording:
+ return (name: string) => {
+ if (this.recordPosesHeartbeat) {
+ clearInterval(this.recordPosesHeartbeat);
+ this.recordPosesHeartbeat = undefined;
+ }
+ this.storageHandler.savePoseRecording(name, this.poses);
+ this.poses = [];
+ };
+ case MovementRecorderFunction.StopRecording:
+ return () => {
+ if (this.recordPosesHeartbeat) {
+ clearInterval(this.recordPosesHeartbeat);
+ this.recordPosesHeartbeat = undefined;
+ }
+ this.poses = [];
+ };
+ case MovementRecorderFunction.SavedRecordingNames:
+ return () => {
+ return this.storageHandler.getRecordingNames();
+ };
+ case MovementRecorderFunction.DeleteRecording:
+ return (recordingID: number) => {
+ let recordingNames =
+ this.storageHandler.getRecordingNames();
+ this.storageHandler.deleteRecording(
+ recordingNames[recordingID],
+ );
+ };
+ case MovementRecorderFunction.DeleteRecordingName:
+ return (name: string) => {
+ this.storageHandler.deleteRecording(name);
+ };
+ case MovementRecorderFunction.LoadRecording:
+ return (recordingID: number) => {
+ let recordingNames =
+ this.storageHandler.getRecordingNames();
+ let recording = this.storageHandler.getRecording(
+ recordingNames[recordingID],
+ );
+ FunctionProvider.remoteRobot?.playbackPoses(recording);
+ };
+ case MovementRecorderFunction.LoadRecordingName:
+ return (name: string) => {
+ let recording = this.storageHandler.getRecording(name);
+ FunctionProvider.remoteRobot?.playbackPoses(recording);
+ };
+ case MovementRecorderFunction.Cancel:
+ return () => FunctionProvider.remoteRobot?.stopTrajectory();
+ }
}
- }
}
diff --git a/src/pages/operator/tsx/function_providers/PredictiveDisplayFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/PredictiveDisplayFunctionProvider.tsx
index dfa6061f..1bb13c70 100644
--- a/src/pages/operator/tsx/function_providers/PredictiveDisplayFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/PredictiveDisplayFunctionProvider.tsx
@@ -4,81 +4,94 @@ import { JOINT_VELOCITIES, JOINT_INCREMENTS, ValidJoints } from "shared/util";
import { ActionMode } from "../utils/component_definitions";
export class PredictiveDisplayFunctionProvider extends FunctionProvider {
- constructor() {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- }
+ constructor() {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ }
- /**
- * Returns a set of functions to execute when
- * the user interacts with predictive display mode
- *
- * @returns the {@link PredictiveDisplayFunctions} for the action modes
- */
- public provideFunctions(
- setActiveCallback: (active: boolean) => void,
- ): PredictiveDisplayFunctions {
- const baseLinVel =
- JOINT_VELOCITIES["translate_mobile_base"]! *
- FunctionProvider.velocityScale;
- const baseAngVel =
- JOINT_VELOCITIES["rotate_mobile_base"]! * FunctionProvider.velocityScale;
- switch (FunctionProvider.actionMode) {
- case ActionMode.StepActions:
- return {
- onClick: (length: number, angle: number) => {
- this.incrementalBaseDrive(baseLinVel * length, baseAngVel * angle);
- setActiveCallback(true);
- setTimeout(() => setActiveCallback(false), 1000);
- },
- onLeave: () => {
- this.stopCurrentAction();
- setActiveCallback(false);
- },
- };
- case ActionMode.PressAndHold:
- return {
- onClick: (length: number, angle: number) => {
- this.continuousBaseDrive(baseLinVel * length, baseAngVel * angle);
- setActiveCallback(true);
- },
- onMove: (length: number, angle: number) =>
- this.activeVelocityAction
- ? this.continuousBaseDrive(
- baseLinVel * length,
- baseAngVel * angle,
- )
- : null,
- onRelease: () => {
- this.stopCurrentAction();
- setActiveCallback(false);
- },
- onLeave: () => {
- this.stopCurrentAction();
- setActiveCallback(false);
- },
- };
- case ActionMode.ClickClick:
- return {
- onClick: (length: number, angle: number) => {
- if (this.activeVelocityAction) {
- this.stopCurrentAction();
- setActiveCallback(false);
- } else {
- this.continuousBaseDrive(baseLinVel * length, baseAngVel * angle);
- setActiveCallback(true);
- }
- },
- onMove: (length: number, angle: number) => {
- if (this.activeVelocityAction) {
- this.continuousBaseDrive(baseLinVel * length, baseAngVel * angle);
- }
- },
- onLeave: () => {
- this.stopCurrentAction();
- setActiveCallback(false);
- },
- };
+ /**
+ * Returns a set of functions to execute when
+ * the user interacts with predictive display mode
+ *
+ * @returns the {@link PredictiveDisplayFunctions} for the action modes
+ */
+ public provideFunctions(
+ setActiveCallback: (active: boolean) => void,
+ ): PredictiveDisplayFunctions {
+ const baseLinVel =
+ JOINT_VELOCITIES["translate_mobile_base"]! *
+ FunctionProvider.velocityScale;
+ const baseAngVel =
+ JOINT_VELOCITIES["rotate_mobile_base"]! *
+ FunctionProvider.velocityScale;
+ switch (FunctionProvider.actionMode) {
+ case ActionMode.StepActions:
+ return {
+ onClick: (length: number, angle: number) => {
+ this.incrementalBaseDrive(
+ baseLinVel * length,
+ baseAngVel * angle,
+ );
+ setActiveCallback(true);
+ setTimeout(() => setActiveCallback(false), 1000);
+ },
+ onLeave: () => {
+ this.stopCurrentAction();
+ setActiveCallback(false);
+ },
+ };
+ case ActionMode.PressAndHold:
+ return {
+ onClick: (length: number, angle: number) => {
+ this.continuousBaseDrive(
+ baseLinVel * length,
+ baseAngVel * angle,
+ );
+ setActiveCallback(true);
+ },
+ onMove: (length: number, angle: number) =>
+ this.activeVelocityAction
+ ? this.continuousBaseDrive(
+ baseLinVel * length,
+ baseAngVel * angle,
+ )
+ : null,
+ onRelease: () => {
+ this.stopCurrentAction();
+ setActiveCallback(false);
+ },
+ onLeave: () => {
+ this.stopCurrentAction();
+ setActiveCallback(false);
+ },
+ };
+ case ActionMode.ClickClick:
+ return {
+ onClick: (length: number, angle: number) => {
+ if (this.activeVelocityAction) {
+ this.stopCurrentAction();
+ setActiveCallback(false);
+ } else {
+ this.continuousBaseDrive(
+ baseLinVel * length,
+ baseAngVel * angle,
+ );
+ setActiveCallback(true);
+ }
+ },
+ onMove: (length: number, angle: number) => {
+ if (this.activeVelocityAction) {
+ this.continuousBaseDrive(
+ baseLinVel * length,
+ baseAngVel * angle,
+ );
+ }
+ },
+ onLeave: () => {
+ this.stopCurrentAction();
+ setActiveCallback(false);
+ },
+ };
+ }
}
- }
}
diff --git a/src/pages/operator/tsx/function_providers/RunStopFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/RunStopFunctionProvider.tsx
index e89e34f5..7fe5fe32 100644
--- a/src/pages/operator/tsx/function_providers/RunStopFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/RunStopFunctionProvider.tsx
@@ -1,40 +1,43 @@
import { FunctionProvider } from "./FunctionProvider";
export type RunStopFunctions = {
- onClick: () => void;
+ onClick: () => void;
};
export class RunStopFunctionProvider extends FunctionProvider {
- private enabled: boolean;
- private runStopStateChangeCallback: (enabled: boolean) => void;
+ private enabled: boolean;
+ private runStopStateChangeCallback: (enabled: boolean) => void;
- constructor() {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- this.updateRunStopState = this.updateRunStopState.bind(this);
- }
+ constructor() {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ this.updateRunStopState = this.updateRunStopState.bind(this);
+ }
- /**
- * Records a callback from the function provider. The callback is called
- * whenever the runstop state changes.
- *
- * @param callback callback to function provider
- */
- public setRunStopStateChangeCallback(callback: (enabled: boolean) => void) {
- this.runStopStateChangeCallback = callback;
- }
+ /**
+ * Records a callback from the function provider. The callback is called
+ * whenever the runstop state changes.
+ *
+ * @param callback callback to function provider
+ */
+ public setRunStopStateChangeCallback(callback: (enabled: boolean) => void) {
+ this.runStopStateChangeCallback = callback;
+ }
- public updateRunStopState(enabled: boolean): void {
- this.enabled = enabled;
- if (this.runStopStateChangeCallback)
- this.runStopStateChangeCallback(this.enabled);
- }
+ public updateRunStopState(enabled: boolean): void {
+ this.enabled = enabled;
+ if (this.runStopStateChangeCallback)
+ this.runStopStateChangeCallback(this.enabled);
+ }
- public provideFunctions(): RunStopFunctions {
- return {
- onClick: () => {
- FunctionProvider.remoteRobot?.setToggle("setRunStop", !this.enabled);
- },
- };
- }
+ public provideFunctions(): RunStopFunctions {
+ return {
+ onClick: () => {
+ FunctionProvider.remoteRobot?.setToggle(
+ "setRunStop",
+ !this.enabled,
+ );
+ },
+ };
+ }
}
diff --git a/src/pages/operator/tsx/function_providers/TextToSpeechFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/TextToSpeechFunctionProvider.tsx
index 7249a897..ed63b8f6 100644
--- a/src/pages/operator/tsx/function_providers/TextToSpeechFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/TextToSpeechFunctionProvider.tsx
@@ -3,40 +3,40 @@ import { TextToSpeechFunction } from "../layout_components/TextToSpeech";
import { StorageHandler } from "../storage_handler/StorageHandler";
export class TextToSpeechFunctionProvider extends FunctionProvider {
- private storageHandler: StorageHandler;
+ private storageHandler: StorageHandler;
- constructor(storageHandler: StorageHandler) {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- this.storageHandler = storageHandler;
- }
+ constructor(storageHandler: StorageHandler) {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ this.storageHandler = storageHandler;
+ }
- public provideFunctions(textToSpeechFunction: TextToSpeechFunction) {
- switch (textToSpeechFunction) {
- case TextToSpeechFunction.Play:
- return (text: string) => {
- FunctionProvider.remoteRobot?.playTextToSpeech(
- text,
- 0, // 0 to queue, 1 to interrupt
- false, // false to speak normally, true to speak slowly
- );
- };
- case TextToSpeechFunction.Stop:
- return () => {
- FunctionProvider.remoteRobot?.stopTextToSpeech();
- };
- case TextToSpeechFunction.SaveText:
- return (text: string) => {
- this.storageHandler.saveText(text);
- };
- case TextToSpeechFunction.DeleteText:
- return (text: string) => {
- this.storageHandler.deleteText(text);
- };
- case TextToSpeechFunction.SavedTexts:
- return () => {
- return this.storageHandler.getSavedTexts();
- };
+ public provideFunctions(textToSpeechFunction: TextToSpeechFunction) {
+ switch (textToSpeechFunction) {
+ case TextToSpeechFunction.Play:
+ return (text: string) => {
+ FunctionProvider.remoteRobot?.playTextToSpeech(
+ text,
+ 0, // 0 to queue, 1 to interrupt
+ false, // false to speak normally, true to speak slowly
+ );
+ };
+ case TextToSpeechFunction.Stop:
+ return () => {
+ FunctionProvider.remoteRobot?.stopTextToSpeech();
+ };
+ case TextToSpeechFunction.SaveText:
+ return (text: string) => {
+ this.storageHandler.saveText(text);
+ };
+ case TextToSpeechFunction.DeleteText:
+ return (text: string) => {
+ this.storageHandler.deleteText(text);
+ };
+ case TextToSpeechFunction.SavedTexts:
+ return () => {
+ return this.storageHandler.getSavedTexts();
+ };
+ }
}
- }
}
diff --git a/src/pages/operator/tsx/function_providers/UnderMapFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/UnderMapFunctionProvider.tsx
index 000edb9b..f473846d 100644
--- a/src/pages/operator/tsx/function_providers/UnderMapFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/UnderMapFunctionProvider.tsx
@@ -4,160 +4,169 @@ import { FunctionProvider } from "./FunctionProvider";
import { resolve } from "path";
export enum UnderMapButton {
- SelectGoal,
- DeleteGoal,
- CancelGoal,
- SaveGoal,
- LoadGoal,
- GetPose,
- GetSavedPoseNames,
- GetSavedPoseTypes,
- GetSavedPoses,
- NavigateToAruco,
- GoalReached,
+ SelectGoal,
+ DeleteGoal,
+ CancelGoal,
+ SaveGoal,
+ LoadGoal,
+ GetPose,
+ GetSavedPoseNames,
+ GetSavedPoseTypes,
+ GetSavedPoses,
+ NavigateToAruco,
+ GoalReached,
}
export class UnderMapFunctionProvider extends FunctionProvider {
- private selectGoal: boolean;
- private storageHandler: StorageHandler;
- private navigationSuccess?: boolean;
- /**
- * Callback function to update the move base state in the operator
- */
- private operatorCallback?: (state: ActionState) => void = undefined;
+ private selectGoal: boolean;
+ private storageHandler: StorageHandler;
+ private navigationSuccess?: boolean;
+ /**
+ * Callback function to update the move base state in the operator
+ */
+ private operatorCallback?: (state: ActionState) => void = undefined;
- constructor(storageHandler: StorageHandler) {
- super();
- this.provideFunctions = this.provideFunctions.bind(this);
- this.selectGoal = false;
- this.storageHandler = storageHandler;
- }
+ constructor(storageHandler: StorageHandler) {
+ super();
+ this.provideFunctions = this.provideFunctions.bind(this);
+ this.selectGoal = false;
+ this.storageHandler = storageHandler;
+ }
- public setMoveBaseState(state: ActionState) {
- if (state.alert_type == "success") this.navigationSuccess = true;
- if (this.operatorCallback) this.operatorCallback(state);
- }
+ public setMoveBaseState(state: ActionState) {
+ if (state.alert_type == "success") this.navigationSuccess = true;
+ if (this.operatorCallback) this.operatorCallback(state);
+ }
- public provideFunctions(button: UnderMapButton) {
- switch (button) {
- case UnderMapButton.SelectGoal:
- return (toggle: boolean) => {
- this.selectGoal = toggle;
- };
- case UnderMapButton.CancelGoal:
- return () => FunctionProvider.remoteRobot?.stopMoveBase();
- case UnderMapButton.DeleteGoal:
- return (idx: number) => {
- let poses = this.storageHandler.getMapPoseNames();
- this.storageHandler.deleteMapPose(poses[idx]);
- };
- case UnderMapButton.SaveGoal:
- return (name: string) => {
- let pose = FunctionProvider.remoteRobot?.getMapPose();
- if (!pose) throw "Cannot save undefined map pose!";
- this.storageHandler.saveMapPose(name, pose, "MAP");
- };
- case UnderMapButton.LoadGoal:
- return (idx: number) => {
- this.navigationSuccess = undefined;
- let poses = this.storageHandler.getMapPoseNames();
- let pose = this.storageHandler.getMapPose(poses[idx]);
- let rosPose = {
- position: {
- x: pose.translation.x,
- y: pose.translation.y,
- z: 0,
- },
- orientation: {
- x: pose.rotation.x,
- y: pose.rotation.y,
- z: pose.rotation.z,
- w: pose.rotation.w,
- },
- } as ROSPose;
- FunctionProvider.remoteRobot?.moveBase(rosPose);
- return pose.translation;
- };
- case UnderMapButton.NavigateToAruco:
- return (idx: number) => {
- let poseTypes = this.storageHandler.getMapPoseTypes();
- if (poseTypes[idx] != "ARUCO") return;
- waitUntil(() => this.navigationSuccess != undefined, 120000).then(
- () => {
- // If navigation failed don't try navigating to marker
- if (!this.navigationSuccess) {
- this.navigationSuccess = undefined;
- return;
- }
+ public provideFunctions(button: UnderMapButton) {
+ switch (button) {
+ case UnderMapButton.SelectGoal:
+ return (toggle: boolean) => {
+ this.selectGoal = toggle;
+ };
+ case UnderMapButton.CancelGoal:
+ return () => FunctionProvider.remoteRobot?.stopMoveBase();
+ case UnderMapButton.DeleteGoal:
+ return (idx: number) => {
+ let poses = this.storageHandler.getMapPoseNames();
+ this.storageHandler.deleteMapPose(poses[idx]);
+ };
+ case UnderMapButton.SaveGoal:
+ return (name: string) => {
+ let pose = FunctionProvider.remoteRobot?.getMapPose();
+ if (!pose) throw "Cannot save undefined map pose!";
+ this.storageHandler.saveMapPose(name, pose, "MAP");
+ };
+ case UnderMapButton.LoadGoal:
+ return (idx: number) => {
+ this.navigationSuccess = undefined;
+ let poses = this.storageHandler.getMapPoseNames();
+ let pose = this.storageHandler.getMapPose(poses[idx]);
+ let rosPose = {
+ position: {
+ x: pose.translation.x,
+ y: pose.translation.y,
+ z: 0,
+ },
+ orientation: {
+ x: pose.rotation.x,
+ y: pose.rotation.y,
+ z: pose.rotation.z,
+ w: pose.rotation.w,
+ },
+ } as ROSPose;
+ FunctionProvider.remoteRobot?.moveBase(rosPose);
+ return pose.translation;
+ };
+ case UnderMapButton.NavigateToAruco:
+ return (idx: number) => {
+ let poseTypes = this.storageHandler.getMapPoseTypes();
+ if (poseTypes[idx] != "ARUCO") return;
+ waitUntil(
+ () => this.navigationSuccess != undefined,
+ 120000,
+ ).then(() => {
+ // If navigation failed don't try navigating to marker
+ if (!this.navigationSuccess) {
+ this.navigationSuccess = undefined;
+ return;
+ }
- this.navigationSuccess = undefined;
- let poseNames = this.storageHandler.getMapPoseNames();
- let name = poseNames[idx];
- let markerNames = this.storageHandler.getArucoMarkerNames();
- let markerIndex = markerNames.indexOf(name);
- if (markerIndex == -1) {
- this.setMoveBaseState({
- state: "Cannot find Aruco Marker",
- alert_type: "error",
- });
- return;
- }
- let markerIDs = this.storageHandler.getArucoMarkerIDs();
- let markerID = markerIDs[markerIndex];
- let marker_info = this.storageHandler.getArucoMarkerInfo();
- let pose = marker_info.aruco_marker_info[markerID].pose;
- if (!pose) {
- this.setMoveBaseState({
- state: "Cannot find Aruco Marker",
- alert_type: "error",
- });
- return;
- }
- FunctionProvider.remoteRobot?.navigateToAruco(name, pose);
- },
- );
- };
- case UnderMapButton.GetPose:
- return () => {
- return FunctionProvider.remoteRobot?.getMapPose();
- };
- case UnderMapButton.GetSavedPoseNames:
- return () => {
- return this.storageHandler.getMapPoseNames();
- };
- case UnderMapButton.GetSavedPoseTypes:
- return () => {
- return this.storageHandler.getMapPoseTypes();
- };
- case UnderMapButton.GetSavedPoses:
- return () => {
- return this.storageHandler.getMapPoses();
- };
- case UnderMapButton.GoalReached:
- return () => {
- const promise = new Promise((resolve, reject) => {
- let interval = setInterval(() => {
- let goalReached = FunctionProvider.remoteRobot?.isGoalReached();
- if (goalReached) {
- clearInterval(interval);
- resolve(true);
- }
- });
- });
- return promise;
- };
- default:
- throw Error(`Cannot get function for unknown UnderMapButton ${button}`);
+ this.navigationSuccess = undefined;
+ let poseNames = this.storageHandler.getMapPoseNames();
+ let name = poseNames[idx];
+ let markerNames =
+ this.storageHandler.getArucoMarkerNames();
+ let markerIndex = markerNames.indexOf(name);
+ if (markerIndex == -1) {
+ this.setMoveBaseState({
+ state: "Cannot find Aruco Marker",
+ alert_type: "error",
+ });
+ return;
+ }
+ let markerIDs = this.storageHandler.getArucoMarkerIDs();
+ let markerID = markerIDs[markerIndex];
+ let marker_info =
+ this.storageHandler.getArucoMarkerInfo();
+ let pose = marker_info.aruco_marker_info[markerID].pose;
+ if (!pose) {
+ this.setMoveBaseState({
+ state: "Cannot find Aruco Marker",
+ alert_type: "error",
+ });
+ return;
+ }
+ FunctionProvider.remoteRobot?.navigateToAruco(
+ name,
+ pose,
+ );
+ });
+ };
+ case UnderMapButton.GetPose:
+ return () => {
+ return FunctionProvider.remoteRobot?.getMapPose();
+ };
+ case UnderMapButton.GetSavedPoseNames:
+ return () => {
+ return this.storageHandler.getMapPoseNames();
+ };
+ case UnderMapButton.GetSavedPoseTypes:
+ return () => {
+ return this.storageHandler.getMapPoseTypes();
+ };
+ case UnderMapButton.GetSavedPoses:
+ return () => {
+ return this.storageHandler.getMapPoses();
+ };
+ case UnderMapButton.GoalReached:
+ return () => {
+ const promise = new Promise((resolve, reject) => {
+ let interval = setInterval(() => {
+ let goalReached =
+ FunctionProvider.remoteRobot?.isGoalReached();
+ if (goalReached) {
+ clearInterval(interval);
+ resolve(true);
+ }
+ });
+ });
+ return promise;
+ };
+ default:
+ throw Error(
+ `Cannot get function for unknown UnderMapButton ${button}`,
+ );
+ }
}
- }
- /**
- * Sets the local pointer to the operator's callback function, to be called
- * whenever the move base state changes.
- *
- * @param callback operator's callback function to update aruco navigation state
- */
- public setOperatorCallback(callback: (state: ActionState) => void) {
- this.operatorCallback = callback;
- }
+ /**
+ * Sets the local pointer to the operator's callback function, to be called
+ * whenever the move base state changes.
+ *
+ * @param callback operator's callback function to update aruco navigation state
+ */
+ public setOperatorCallback(callback: (state: ActionState) => void) {
+ this.operatorCallback = callback;
+ }
}
diff --git a/src/pages/operator/tsx/function_providers/UnderVideoFunctionProvider.tsx b/src/pages/operator/tsx/function_providers/UnderVideoFunctionProvider.tsx
index fccfbbe5..9368cba7 100644
--- a/src/pages/operator/tsx/function_providers/UnderVideoFunctionProvider.tsx
+++ b/src/pages/operator/tsx/function_providers/UnderVideoFunctionProvider.tsx
@@ -1,310 +1,332 @@
import { FunctionProvider } from "./FunctionProvider";
import {
- ActionState,
- CENTER_WRIST,
- Marker,
- REALSENSE_BASE_POSE,
- REALSENSE_FORWARD_POSE,
- REALSENSE_GRIPPER_POSE,
- RobotPose,
- STOW_WRIST_GRIPPER,
- STOW_WRIST_TABLET,
- TabletOrientation,
- TABLET_ORIENTATION_LANDSCAPE,
- TABLET_ORIENTATION_PORTRAIT,
- StretchTool,
+ ActionState,
+ CENTER_WRIST,
+ Marker,
+ REALSENSE_BASE_POSE,
+ REALSENSE_FORWARD_POSE,
+ REALSENSE_GRIPPER_POSE,
+ RobotPose,
+ STOW_WRIST_GRIPPER,
+ STOW_WRIST_TABLET,
+ TabletOrientation,
+ TABLET_ORIENTATION_LANDSCAPE,
+ TABLET_ORIENTATION_PORTRAIT,
+ StretchTool,
} from "../../../../shared/util";
import { stretchTool } from "..";
export enum UnderVideoButton {
- DriveView = "Drive View",
- GripperView = "Gripper View",
- LookAtGripper = "Look At Gripper",
- LookAtBase = "Look At Base",
- LookAhead = "Look Ahead",
- FollowGripper = "Follow Gripper",
- RealsenseDepthSensing = "Realsense Depth Sensing",
- GripperDepthSensing = "Gripper Depth Sensing",
- ExpandedGripperView = "Expanded Gripper View",
- ToggleArucoMarkers = "Toggle Aruco Markers",
- CenterWrist = "Center Wrist",
- StowWrist = "Stow Wrist",
- StartMoveToPregraspHorizontal = "Gripper Horizontal",
- StartMoveToPregraspVertical = "Gripper Vertical",
- CancelMoveToPregrasp = "Cancel Goal",
- MoveToPregraspGoalReached = "Goal Reached",
- ToggleTabletOrientation = "Toggle Tablet Orientation",
- GetTabletOrientation = "Get Tablet Orientation",
+ DriveView = "Drive View",
+ GripperView = "Gripper View",
+ LookAtGripper = "Look At Gripper",
+ LookAtBase = "Look At Base",
+ LookAhead = "Look Ahead",
+ FollowGripper = "Follow Gripper",
+ RealsenseDepthSensing = "Realsense Depth Sensing",
+ GripperDepthSensing = "Gripper Depth Sensing",
+ ExpandedGripperView = "Expanded Gripper View",
+ ToggleArucoMarkers = "Toggle Aruco Markers",
+ CenterWrist = "Center Wrist",
+ StowWrist = "Stow Wrist",
+ StartMoveToPregraspHorizontal = "Gripper Horizontal",
+ StartMoveToPregraspVertical = "Gripper Vertical",
+ CancelMoveToPregrasp = "Cancel Goal",
+ MoveToPregraspGoalReached = "Goal Reached",
+ ToggleTabletOrientation = "Toggle Tablet Orientation",
+ GetTabletOrientation = "Get Tablet Orientation",
}
/** Array of different perspectives for the overhead camera */
export const overheadButtons: UnderVideoButton[] = [
- UnderVideoButton.DriveView,
- UnderVideoButton.GripperView,
+ UnderVideoButton.DriveView,
+ UnderVideoButton.GripperView,
];
/** Type to specify the different overhead camera perspectives */
export type OverheadButtons = (typeof overheadButtons)[number];
/** Array of different perspectives for the realsense camera */
export const realsenseButtons: UnderVideoButton[] = [
- UnderVideoButton.LookAhead,
- UnderVideoButton.LookAtBase,
- UnderVideoButton.LookAtGripper,
+ UnderVideoButton.LookAhead,
+ UnderVideoButton.LookAtBase,
+ UnderVideoButton.LookAtGripper,
];
/** Array of different options for the MoveToPregrasp feature on the realsense camera */
export const realsenseMoveToPregraspButtons: UnderVideoButton[] = [
- UnderVideoButton.StartMoveToPregraspHorizontal,
- UnderVideoButton.StartMoveToPregraspVertical,
+ UnderVideoButton.StartMoveToPregraspHorizontal,
+ UnderVideoButton.StartMoveToPregraspVertical,
];
/** Array of different actions for the wrist */
export const wristButtons: UnderVideoButton[] = [
- UnderVideoButton.CenterWrist,
- UnderVideoButton.StowWrist,
+ UnderVideoButton.CenterWrist,
+ UnderVideoButton.StowWrist,
];
/** Type to specify the different realsense camera perspectives */
export type RealsenseButtons = (typeof realsenseButtons)[number];
export type UnderVideoButtonFunctions = {
- onClick?: (...args: any[]) => void;
- onCheck?: (toggle: boolean) => void;
- get?: () => any;
- send?: (name: string) => void;
- getFuture?: () => Promise;
+ onClick?: (...args: any[]) => void;
+ onCheck?: (toggle: boolean) => void;
+ get?: () => any;
+ send?: (name: string) => void;
+ getFuture?: () => Promise;
};
export class UnderVideoFunctionProvider extends FunctionProvider {
- private tabletOrientation: TabletOrientation;
+ private tabletOrientation: TabletOrientation;
- constructor() {
- super();
- this.tabletOrientation = TabletOrientation.LANDSCAPE;
- this.provideFunctions = this.provideFunctions.bind(this);
- this.jointStateCallback = this.jointStateCallback.bind(this);
- }
+ constructor() {
+ super();
+ this.tabletOrientation = TabletOrientation.LANDSCAPE;
+ this.provideFunctions = this.provideFunctions.bind(this);
+ this.jointStateCallback = this.jointStateCallback.bind(this);
+ }
- /**
- * Callback function for when the tablet orientation changes
- */
- private tabletOrientationOperatorCallback?: (
- tabletOrientation: TabletOrientation,
- ) => void = undefined;
- /**
- * Callback function to update the move base state in the operator
- * interface (e.g., show alerts).
- */
- private moveToPregraspOperatorCallback?: (state: ActionState) => void =
- undefined;
- /**
- * Store the timestam at which the last moveToPregrasp state was received
- */
- private lastMoveToPregraspStateTimestamp: number = 0;
+ /**
+ * Callback function for when the tablet orientation changes
+ */
+ private tabletOrientationOperatorCallback?: (
+ tabletOrientation: TabletOrientation,
+ ) => void = undefined;
+ /**
+ * Callback function to update the move base state in the operator
+ * interface (e.g., show alerts).
+ */
+ private moveToPregraspOperatorCallback?: (state: ActionState) => void =
+ undefined;
+ /**
+ * Store the timestam at which the last moveToPregrasp state was received
+ */
+ private lastMoveToPregraspStateTimestamp: number = 0;
- /**
- * Called when a response is received from the robot for the move to pregrasp.
- * @param state the move to pregrasp state to set
- */
- public setMoveToPregraspState(state: ActionState) {
- this.lastMoveToPregraspStateTimestamp = Date.now();
- if (this.moveToPregraspOperatorCallback)
- this.moveToPregraspOperatorCallback(state);
- }
+ /**
+ * Called when a response is received from the robot for the move to pregrasp.
+ * @param state the move to pregrasp state to set
+ */
+ public setMoveToPregraspState(state: ActionState) {
+ this.lastMoveToPregraspStateTimestamp = Date.now();
+ if (this.moveToPregraspOperatorCallback)
+ this.moveToPregraspOperatorCallback(state);
+ }
- public provideFunctions(button: UnderVideoButton): UnderVideoButtonFunctions {
- let horizontal = false;
- switch (button) {
- case UnderVideoButton.DriveView:
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setCameraPerspective(
- "overhead",
- "nav",
- ),
- };
- case UnderVideoButton.GripperView:
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setCameraPerspective(
- "overhead",
- "manip",
- ),
- };
- case UnderVideoButton.LookAtBase:
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setRobotPose(REALSENSE_BASE_POSE),
- };
- case UnderVideoButton.LookAhead:
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setRobotPose(REALSENSE_FORWARD_POSE),
- };
- case UnderVideoButton.LookAtGripper:
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.lookAtGripper("lookAtGripper"), //setRobotPose(REALSENSE_GRIPPER_POSE)
- };
- case UnderVideoButton.FollowGripper:
- return {
- onCheck: (toggle: boolean) =>
- FunctionProvider.remoteRobot?.setToggle("setFollowGripper", toggle),
- };
- case UnderVideoButton.RealsenseDepthSensing:
- return {
- onCheck: (toggle: boolean) =>
- FunctionProvider.remoteRobot?.setToggle(
- "setRealsenseDepthSensing",
- toggle,
- ),
- };
- case UnderVideoButton.GripperDepthSensing:
- return {
- onCheck: (toggle: boolean) =>
- FunctionProvider.remoteRobot?.setToggle(
- "setGripperDepthSensing",
- toggle,
- ),
- };
- case UnderVideoButton.ExpandedGripperView:
- return {
- onCheck: (toggle: boolean) =>
- FunctionProvider.remoteRobot?.setToggle(
- "setExpandedGripper",
- toggle,
- ),
- };
- case UnderVideoButton.StartMoveToPregraspHorizontal:
- horizontal = true;
- case UnderVideoButton.StartMoveToPregraspVertical:
- return {
- onClick: (scaledXY: [number, number] | null) => {
- if (!scaledXY) {
- console.log("No scaledXY");
- return;
- }
- FunctionProvider.remoteRobot?.moveToPregrasp(
- scaledXY[0],
- scaledXY[1],
- horizontal,
- );
- },
- };
- case UnderVideoButton.MoveToPregraspGoalReached:
- // TODO: Add timeouts to this and the other GoalReached promises!
- return {
- getFuture: () => {
- let currentTimestamp = Date.now();
- let that = this;
- const promise = new Promise((resolve, reject) => {
- let interval = setInterval(() => {
- let goalReached =
- that.lastMoveToPregraspStateTimestamp > currentTimestamp;
- if (goalReached) {
- clearInterval(interval);
- resolve(true);
+ public provideFunctions(
+ button: UnderVideoButton,
+ ): UnderVideoButtonFunctions {
+ let horizontal = false;
+ switch (button) {
+ case UnderVideoButton.DriveView:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setCameraPerspective(
+ "overhead",
+ "nav",
+ ),
+ };
+ case UnderVideoButton.GripperView:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setCameraPerspective(
+ "overhead",
+ "manip",
+ ),
+ };
+ case UnderVideoButton.LookAtBase:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setRobotPose(
+ REALSENSE_BASE_POSE,
+ ),
+ };
+ case UnderVideoButton.LookAhead:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setRobotPose(
+ REALSENSE_FORWARD_POSE,
+ ),
+ };
+ case UnderVideoButton.LookAtGripper:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.lookAtGripper(
+ "lookAtGripper",
+ ), //setRobotPose(REALSENSE_GRIPPER_POSE)
+ };
+ case UnderVideoButton.FollowGripper:
+ return {
+ onCheck: (toggle: boolean) =>
+ FunctionProvider.remoteRobot?.setToggle(
+ "setFollowGripper",
+ toggle,
+ ),
+ };
+ case UnderVideoButton.RealsenseDepthSensing:
+ return {
+ onCheck: (toggle: boolean) =>
+ FunctionProvider.remoteRobot?.setToggle(
+ "setRealsenseDepthSensing",
+ toggle,
+ ),
+ };
+ case UnderVideoButton.GripperDepthSensing:
+ return {
+ onCheck: (toggle: boolean) =>
+ FunctionProvider.remoteRobot?.setToggle(
+ "setGripperDepthSensing",
+ toggle,
+ ),
+ };
+ case UnderVideoButton.ExpandedGripperView:
+ return {
+ onCheck: (toggle: boolean) =>
+ FunctionProvider.remoteRobot?.setToggle(
+ "setExpandedGripper",
+ toggle,
+ ),
+ };
+ case UnderVideoButton.StartMoveToPregraspHorizontal:
+ horizontal = true;
+ case UnderVideoButton.StartMoveToPregraspVertical:
+ return {
+ onClick: (scaledXY: [number, number] | null) => {
+ if (!scaledXY) {
+ console.log("No scaledXY");
+ return;
+ }
+ FunctionProvider.remoteRobot?.moveToPregrasp(
+ scaledXY[0],
+ scaledXY[1],
+ horizontal,
+ );
+ },
+ };
+ case UnderVideoButton.MoveToPregraspGoalReached:
+ // TODO: Add timeouts to this and the other GoalReached promises!
+ return {
+ getFuture: () => {
+ let currentTimestamp = Date.now();
+ let that = this;
+ const promise = new Promise((resolve, reject) => {
+ let interval = setInterval(() => {
+ let goalReached =
+ that.lastMoveToPregraspStateTimestamp >
+ currentTimestamp;
+ if (goalReached) {
+ clearInterval(interval);
+ resolve(true);
+ }
+ });
+ });
+ return promise;
+ },
+ };
+ case UnderVideoButton.CancelMoveToPregrasp:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.stopMoveToPregrasp(),
+ };
+ case UnderVideoButton.ToggleArucoMarkers:
+ return {
+ onCheck: (toggle: boolean) =>
+ FunctionProvider.remoteRobot?.setToggle(
+ "setArucoMarkers",
+ toggle,
+ ),
+ };
+ case UnderVideoButton.CenterWrist:
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setRobotPose(
+ CENTER_WRIST,
+ ),
+ };
+ case UnderVideoButton.StowWrist:
+ if (stretchTool === StretchTool.TABLET) {
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setRobotPose(
+ STOW_WRIST_TABLET,
+ ),
+ };
+ } else {
+ return {
+ onClick: () =>
+ FunctionProvider.remoteRobot?.setRobotPose(
+ STOW_WRIST_GRIPPER,
+ ),
+ };
}
- });
- });
- return promise;
- },
- };
- case UnderVideoButton.CancelMoveToPregrasp:
- return {
- onClick: () => FunctionProvider.remoteRobot?.stopMoveToPregrasp(),
- };
- case UnderVideoButton.ToggleArucoMarkers:
- return {
- onCheck: (toggle: boolean) =>
- FunctionProvider.remoteRobot?.setToggle("setArucoMarkers", toggle),
- };
- case UnderVideoButton.CenterWrist:
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setRobotPose(CENTER_WRIST),
- };
- case UnderVideoButton.StowWrist:
- if (stretchTool === StretchTool.TABLET) {
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setRobotPose(STOW_WRIST_TABLET),
- };
- } else {
- return {
- onClick: () =>
- FunctionProvider.remoteRobot?.setRobotPose(STOW_WRIST_GRIPPER),
- };
+ case UnderVideoButton.ToggleTabletOrientation:
+ return {
+ onCheck: (isPortrait: boolean) =>
+ FunctionProvider.remoteRobot?.setRobotPose(
+ isPortrait
+ ? TABLET_ORIENTATION_LANDSCAPE
+ : TABLET_ORIENTATION_PORTRAIT,
+ ),
+ };
+ case UnderVideoButton.GetTabletOrientation:
+ return {
+ get: () => {
+ return this.tabletOrientation;
+ },
+ };
+ default:
+ throw Error(
+ `Cannot get function for unknown UnderVideoButton ${button}`,
+ );
}
- case UnderVideoButton.ToggleTabletOrientation:
- return {
- onCheck: (isPortrait: boolean) =>
- FunctionProvider.remoteRobot?.setRobotPose(
- isPortrait
- ? TABLET_ORIENTATION_LANDSCAPE
- : TABLET_ORIENTATION_PORTRAIT,
- ),
- };
- case UnderVideoButton.GetTabletOrientation:
- return {
- get: () => {
- return this.tabletOrientation;
- },
- };
- default:
- throw Error(
- `Cannot get function for unknown UnderVideoButton ${button}`,
- );
}
- }
- /**
- * Callback for when a new joint state is received.
- */
- public jointStateCallback(robotPose: RobotPose) {
- let prevTabletOrientation = this.tabletOrientation;
- // Update the tablet orientation
- let wristRoll = 0.0;
- if (robotPose.joint_wrist_roll) {
- wristRoll = robotPose.joint_wrist_roll;
- }
- let diff = Math.abs(wristRoll % Math.PI);
- let isLandscape = false;
- if (diff <= Math.PI / 4.0 || diff >= (3.0 * Math.PI) / 4.0) {
- isLandscape = true;
- }
- if (isLandscape) {
- this.tabletOrientation = TabletOrientation.LANDSCAPE;
- } else {
- this.tabletOrientation = TabletOrientation.PORTRAIT;
+ /**
+ * Callback for when a new joint state is received.
+ */
+ public jointStateCallback(robotPose: RobotPose) {
+ let prevTabletOrientation = this.tabletOrientation;
+ // Update the tablet orientation
+ let wristRoll = 0.0;
+ if (robotPose.joint_wrist_roll) {
+ wristRoll = robotPose.joint_wrist_roll;
+ }
+ let diff = Math.abs(wristRoll % Math.PI);
+ let isLandscape = false;
+ if (diff <= Math.PI / 4.0 || diff >= (3.0 * Math.PI) / 4.0) {
+ isLandscape = true;
+ }
+ if (isLandscape) {
+ this.tabletOrientation = TabletOrientation.LANDSCAPE;
+ } else {
+ this.tabletOrientation = TabletOrientation.PORTRAIT;
+ }
+ if (
+ this.tabletOrientationOperatorCallback &&
+ prevTabletOrientation !== this.tabletOrientation
+ ) {
+ this.tabletOrientationOperatorCallback(this.tabletOrientation);
+ }
}
- if (
- this.tabletOrientationOperatorCallback &&
- prevTabletOrientation !== this.tabletOrientation
+
+ /**
+ * Sets the local pointer to the operator's callback function, to be called
+ * whenever the move to pregrasp state changes.
+ *
+ * @param callback operator's callback function to update aruco navigation state
+ */
+ public setMoveToPregraspOperatorCallback(
+ callback: (state: ActionState) => void,
) {
- this.tabletOrientationOperatorCallback(this.tabletOrientation);
+ this.moveToPregraspOperatorCallback = callback;
}
- }
- /**
- * Sets the local pointer to the operator's callback function, to be called
- * whenever the move to pregrasp state changes.
- *
- * @param callback operator's callback function to update aruco navigation state
- */
- public setMoveToPregraspOperatorCallback(
- callback: (state: ActionState) => void,
- ) {
- this.moveToPregraspOperatorCallback = callback;
- }
-
- /**
- * Sets the local pointer to the operator's callback function, to be called
- * whenever the tablet orientation changes.
- *
- * @param callback operator's callback function to update aruco navigation state
- */
- public setTabletOrientationOperatorCallback(
- callback: (tabletOrientation: TabletOrientation) => void,
- ) {
- this.tabletOrientationOperatorCallback = callback;
- }
+ /**
+ * Sets the local pointer to the operator's callback function, to be called
+ * whenever the tablet orientation changes.
+ *
+ * @param callback operator's callback function to update aruco navigation state
+ */
+ public setTabletOrientationOperatorCallback(
+ callback: (tabletOrientation: TabletOrientation) => void,
+ ) {
+ this.tabletOrientationOperatorCallback = callback;
+ }
}
diff --git a/src/pages/operator/tsx/index.tsx b/src/pages/operator/tsx/index.tsx
index 3ac2dff9..d73d69b4 100644
--- a/src/pages/operator/tsx/index.tsx
+++ b/src/pages/operator/tsx/index.tsx
@@ -2,14 +2,14 @@ import React from "react";
import { createRoot, Root } from "react-dom/client";
import { WebRTCConnection } from "shared/webrtcconnections";
import {
- WebRTCMessage,
- RemoteStream,
- RobotPose,
- ROSOccupancyGrid,
- StretchTool,
- delay,
- getStretchTool,
- waitUntil,
+ WebRTCMessage,
+ RemoteStream,
+ RobotPose,
+ ROSOccupancyGrid,
+ StretchTool,
+ delay,
+ getStretchTool,
+ waitUntil,
} from "shared/util";
import { RemoteRobot } from "shared/remoterobot";
import { cmd } from "shared/commands";
@@ -35,8 +35,8 @@ import { BatteryVoltageFunctionProvider } from "./function_providers/BatteryVolt
import { waitUntilAsync } from "../../../shared/util";
let allRemoteStreams: Map = new Map<
- string,
- RemoteStream
+ string,
+ RemoteStream
>();
let remoteRobot: RemoteRobot;
let connection: WebRTCConnection;
@@ -50,11 +50,11 @@ export let storageHandler: StorageHandler;
// components and remote robot.
export var buttonFunctionProvider = new ButtonFunctionProvider();
export var predicitiveDisplayFunctionProvider =
- new PredictiveDisplayFunctionProvider();
+ new PredictiveDisplayFunctionProvider();
export var underVideoFunctionProvider = new UnderVideoFunctionProvider();
export var runStopFunctionProvider = new RunStopFunctionProvider();
export var batteryVoltageFunctionProvider =
- new BatteryVoltageFunctionProvider();
+ new BatteryVoltageFunctionProvider();
export var mapFunctionProvider: MapFunctionProvider;
export var underMapFunctionProvider: UnderMapFunctionProvider;
export var movementRecorderFunctionProvider: MovementRecorderFunctionProvider;
@@ -62,53 +62,53 @@ export var textToSpeechFunctionProvider: TextToSpeechFunctionProvider;
// Create the WebRTC connection and connect the operator room
connection = new WebRTCConnection({
- peerRole: "operator",
- polite: true,
- onMessage: handleWebRTCMessage,
- onTrackAdded: handleRemoteTrackAdded,
- onMessageChannelOpen: configureRemoteRobot,
- onConnectionEnd: disconnectFromRobot,
+ peerRole: "operator",
+ polite: true,
+ onMessage: handleWebRTCMessage,
+ onTrackAdded: handleRemoteTrackAdded,
+ onMessageChannelOpen: configureRemoteRobot,
+ onConnectionEnd: disconnectFromRobot,
});
new Promise(async (resolve) => {
- let connected = false;
- while (!connected) {
- connection.hangup();
+ let connected = false;
+ while (!connected) {
+ connection.hangup();
- // Attempt to join robot room
- let joinedRobotRoom = await connection.addOperatorToRobotRoom();
- if (!joinedRobotRoom) {
- console.log("Operator failed to join robot room");
- await delay(500);
- continue;
- }
+ // Attempt to join robot room
+ let joinedRobotRoom = await connection.addOperatorToRobotRoom();
+ if (!joinedRobotRoom) {
+ console.log("Operator failed to join robot room");
+ await delay(500);
+ continue;
+ }
- // Wait for WebRTC connection to resolve, timeout after 10 seconds
- let isResolved = await waitUntil(
- () => connection.connectionState() == "connected",
- 10000,
- );
- if (!isResolved) {
- console.warn("WebRTC connection could not resolve");
- await delay(500);
- continue;
- }
+ // Wait for WebRTC connection to resolve, timeout after 10 seconds
+ let isResolved = await waitUntil(
+ () => connection.connectionState() == "connected",
+ 10000,
+ );
+ if (!isResolved) {
+ console.warn("WebRTC connection could not resolve");
+ await delay(500);
+ continue;
+ }
- // Wait for data to flow through the data channel, timeout after 10 seconds
- connected = await waitUntilAsync(
- async () => await connection.isConnected(),
- 10000,
- );
- if (!connected) {
- console.warn("No data flowing through data channel");
- await delay(500);
- continue;
- }
+ // Wait for data to flow through the data channel, timeout after 10 seconds
+ connected = await waitUntilAsync(
+ async () => await connection.isConnected(),
+ 10000,
+ );
+ if (!connected) {
+ console.warn("No data flowing through data channel");
+ await delay(500);
+ continue;
+ }
- await delay(1000); // 1 second delay to allow data to flow through data channel
- initializeOperator();
- resolve();
- }
+ await delay(1000); // 1 second delay to allow data to flow through data channel
+ initializeOperator();
+ resolve();
+ }
});
// Create root once when index is loaded
@@ -117,20 +117,20 @@ root = createRoot(container!);
/** Handle when the WebRTC connection adds a new track on a camera video stream. */
function handleRemoteTrackAdded(event: RTCTrackEvent) {
- const track = event.track;
- const stream = event.streams[0];
- let streamName = connection.cameraInfo[stream.id];
- console.log("Adding remote track", streamName);
- if (streamName != "audio") {
- console.log(stream.getVideoTracks()[0].getConstraints());
- }
- console.log("got track id=" + track.id, track);
- if (stream) {
- console.log("stream id=" + stream.id, stream);
- }
- console.log("OPERATOR: adding remote tracks");
+ const track = event.track;
+ const stream = event.streams[0];
+ let streamName = connection.cameraInfo[stream.id];
+ console.log("Adding remote track", streamName);
+ if (streamName != "audio") {
+ console.log(stream.getVideoTracks()[0].getConstraints());
+ }
+ console.log("got track id=" + track.id, track);
+ if (stream) {
+ console.log("stream id=" + stream.id, stream);
+ }
+ console.log("OPERATOR: adding remote tracks");
- allRemoteStreams.set(streamName, { track: track, stream: stream });
+ allRemoteStreams.set(streamName, { track: track, stream: stream });
}
/**
@@ -138,63 +138,65 @@ function handleRemoteTrackAdded(event: RTCTrackEvent) {
* @param message the {@link WebRTCMessage} or an array of messages.
*/
function handleWebRTCMessage(message: WebRTCMessage | WebRTCMessage[]) {
- if (message instanceof Array) {
- for (const subMessage of message) {
- // Recursive call to handle each message in the array
- handleWebRTCMessage(subMessage);
+ if (message instanceof Array) {
+ for (const subMessage of message) {
+ // Recursive call to handle each message in the array
+ handleWebRTCMessage(subMessage);
+ }
+ return;
}
- return;
- }
- switch (message.type) {
- case "validJointState":
- remoteRobot.sensors.checkValidJointState(
- message.robotPose,
- message.jointsInLimits,
- message.jointsInCollision,
- );
- break;
- case "isRunStopped":
- remoteRobot.sensors.setRunStopState(message.enabled);
- break;
- case "hasBetaTeleopKit":
- hasBetaTeleopKit = message.value;
- break;
- case "stretchTool":
- console.log("index stretchTool", message.value);
- stretchTool = getStretchTool(message.value);
- break;
- case "occupancyGrid":
- if (!occupancyGrid) {
- occupancyGrid = message.message;
- } else {
- occupancyGrid.data = occupancyGrid.data.concat(message.message.data);
- }
- break;
- case "amclPose":
- remoteRobot.setMapPose(message.message);
- break;
- case "goalStatus":
- console.log("goalStatus", message.message);
- remoteRobot.setGoalReached(true);
- break;
- case "moveBaseState":
- console.log("moveBaseState", message.message);
- underMapFunctionProvider.setMoveBaseState(message.message);
- break;
- case "moveToPregraspState":
- console.log("moveToPregraspState", message.message);
- underVideoFunctionProvider.setMoveToPregraspState(message.message);
- break;
- case "relativePose":
- remoteRobot.setRelativePose(message.message);
- break;
- case "batteryVoltage":
- remoteRobot.sensors.setBatteryVoltage(message.message);
- break;
- default:
- throw Error(`unhandled WebRTC message type ${message.type}`);
- }
+ switch (message.type) {
+ case "validJointState":
+ remoteRobot.sensors.checkValidJointState(
+ message.robotPose,
+ message.jointsInLimits,
+ message.jointsInCollision,
+ );
+ break;
+ case "isRunStopped":
+ remoteRobot.sensors.setRunStopState(message.enabled);
+ break;
+ case "hasBetaTeleopKit":
+ hasBetaTeleopKit = message.value;
+ break;
+ case "stretchTool":
+ console.log("index stretchTool", message.value);
+ stretchTool = getStretchTool(message.value);
+ break;
+ case "occupancyGrid":
+ if (!occupancyGrid) {
+ occupancyGrid = message.message;
+ } else {
+ occupancyGrid.data = occupancyGrid.data.concat(
+ message.message.data,
+ );
+ }
+ break;
+ case "amclPose":
+ remoteRobot.setMapPose(message.message);
+ break;
+ case "goalStatus":
+ console.log("goalStatus", message.message);
+ remoteRobot.setGoalReached(true);
+ break;
+ case "moveBaseState":
+ console.log("moveBaseState", message.message);
+ underMapFunctionProvider.setMoveBaseState(message.message);
+ break;
+ case "moveToPregraspState":
+ console.log("moveToPregraspState", message.message);
+ underVideoFunctionProvider.setMoveToPregraspState(message.message);
+ break;
+ case "relativePose":
+ remoteRobot.setRelativePose(message.message);
+ break;
+ case "batteryVoltage":
+ remoteRobot.sensors.setBatteryVoltage(message.message);
+ break;
+ default:
+ throw Error(`unhandled WebRTC message type ${message.type}`);
+ }
}
/**
@@ -202,18 +204,18 @@ function handleWebRTCMessage(message: WebRTCMessage | WebRTCMessage[]) {
* and renders the operator browser.
*/
function initializeOperator() {
- // configureRemoteRobot();
- const storageHandlerReadyCallback = () => {
- underMapFunctionProvider = new UnderMapFunctionProvider(storageHandler);
- movementRecorderFunctionProvider = new MovementRecorderFunctionProvider(
- storageHandler,
- );
- textToSpeechFunctionProvider = new TextToSpeechFunctionProvider(
- storageHandler,
- );
- renderOperator(storageHandler);
- };
- storageHandler = createStorageHandler(storageHandlerReadyCallback);
+ // configureRemoteRobot();
+ const storageHandlerReadyCallback = () => {
+ underMapFunctionProvider = new UnderMapFunctionProvider(storageHandler);
+ movementRecorderFunctionProvider = new MovementRecorderFunctionProvider(
+ storageHandler,
+ );
+ textToSpeechFunctionProvider = new TextToSpeechFunctionProvider(
+ storageHandler,
+ );
+ renderOperator(storageHandler);
+ };
+ storageHandler = createStorageHandler(storageHandlerReadyCallback);
}
/**
@@ -221,26 +223,26 @@ function initializeOperator() {
* WebRTC connection.
*/
function configureRemoteRobot() {
- remoteRobot = new RemoteRobot({
- robotChannel: (message: cmd) => connection.sendData(message),
- });
- occupancyGrid = undefined;
- remoteRobot.getHasBetaTeleopKit("getHasBetaTeleopKit");
- remoteRobot.getStretchTool("getStretchTool");
- FunctionProvider.addRemoteRobot(remoteRobot);
- mapFunctionProvider = new MapFunctionProvider();
- remoteRobot.sensors.setFunctionProviderCallback(
- buttonFunctionProvider.updateJointStates,
- );
- remoteRobot.sensors.setJointStateFunctionProviderCallback(
- underVideoFunctionProvider.jointStateCallback,
- );
- remoteRobot.sensors.setBatteryFunctionProviderCallback(
- batteryVoltageFunctionProvider.updateVoltage,
- );
- remoteRobot.sensors.setRunStopFunctionProviderCallback(
- runStopFunctionProvider.updateRunStopState,
- );
+ remoteRobot = new RemoteRobot({
+ robotChannel: (message: cmd) => connection.sendData(message),
+ });
+ occupancyGrid = undefined;
+ remoteRobot.getHasBetaTeleopKit("getHasBetaTeleopKit");
+ remoteRobot.getStretchTool("getStretchTool");
+ FunctionProvider.addRemoteRobot(remoteRobot);
+ mapFunctionProvider = new MapFunctionProvider();
+ remoteRobot.sensors.setFunctionProviderCallback(
+ buttonFunctionProvider.updateJointStates,
+ );
+ remoteRobot.sensors.setJointStateFunctionProviderCallback(
+ underVideoFunctionProvider.jointStateCallback,
+ );
+ remoteRobot.sensors.setBatteryFunctionProviderCallback(
+ batteryVoltageFunctionProvider.updateVoltage,
+ );
+ remoteRobot.sensors.setRunStopFunctionProviderCallback(
+ runStopFunctionProvider.updateRunStopState,
+ );
}
/**
@@ -250,21 +252,24 @@ function configureRemoteRobot() {
* @returns the storage handler
*/
function createStorageHandler(storageHandlerReadyCallback: () => void) {
- switch (process.env.storage) {
- case "firebase":
- const config: FirebaseOptions = {
- apiKey: process.env.apiKey,
- authDomain: process.env.authDomain,
- projectId: process.env.projectId,
- storageBucket: process.env.storageBucket,
- messagingSenderId: process.env.messagingSenderId,
- appId: process.env.appId,
- measurementId: process.env.measurementId,
- };
- return new FirebaseStorageHandler(storageHandlerReadyCallback, config);
- default:
- return new LocalStorageHandler(storageHandlerReadyCallback);
- }
+ switch (process.env.storage) {
+ case "firebase":
+ const config: FirebaseOptions = {
+ apiKey: process.env.apiKey,
+ authDomain: process.env.authDomain,
+ projectId: process.env.projectId,
+ storageBucket: process.env.storageBucket,
+ messagingSenderId: process.env.messagingSenderId,
+ appId: process.env.appId,
+ measurementId: process.env.measurementId,
+ };
+ return new FirebaseStorageHandler(
+ storageHandlerReadyCallback,
+ config,
+ );
+ default:
+ return new LocalStorageHandler(storageHandlerReadyCallback);
+ }
}
/**
@@ -273,61 +278,61 @@ function createStorageHandler(storageHandlerReadyCallback: () => void) {
* @param storageHandler the storage handler
*/
function renderOperator(storageHandler: StorageHandler) {
- const layout = storageHandler.loadCurrentLayoutOrDefault();
- FunctionProvider.initialize(DEFAULT_VELOCITY_SCALE, layout.actionMode);
+ const layout = storageHandler.loadCurrentLayoutOrDefault();
+ FunctionProvider.initialize(DEFAULT_VELOCITY_SCALE, layout.actionMode);
- !isMobile
- ? root.render(
- ,
- )
- : root.render(
- ,
- );
+ !isMobile
+ ? root.render(
+ ,
+ )
+ : root.render(
+ ,
+ );
- if (!isMobile) {
- var loader = document.createElement("div");
- loader.className = "loader";
- var loaderText = document.createElement("div");
- loaderText.className = "reconnecting-text";
- var text = document.createElement("p");
- text.textContent = "Reconnecting...";
- loaderText.appendChild(text);
- var loaderBackground = document.createElement("div");
- loaderBackground.className = "loader-background";
+ if (!isMobile) {
+ var loader = document.createElement("div");
+ loader.className = "loader";
+ var loaderText = document.createElement("div");
+ loaderText.className = "reconnecting-text";
+ var text = document.createElement("p");
+ text.textContent = "Reconnecting...";
+ loaderText.appendChild(text);
+ var loaderBackground = document.createElement("div");
+ loaderBackground.className = "loader-background";
- setInterval(async () => {
- let connected = await connection.isConnected();
- if (!connected && !window.document.body.contains(loader)) {
- window.document.body.appendChild(loaderBackground);
- window.document.body.appendChild(loaderText);
- window.document.body.appendChild(loader);
- } else if (connected && window.document.body.contains(loader)) {
- window.document.body.removeChild(loaderBackground);
- window.document.body.removeChild(loaderText);
- window.document.body.removeChild(loader);
- }
- }, 1000);
- }
+ setInterval(async () => {
+ let connected = await connection.isConnected();
+ if (!connected && !window.document.body.contains(loader)) {
+ window.document.body.appendChild(loaderBackground);
+ window.document.body.appendChild(loaderText);
+ window.document.body.appendChild(loader);
+ } else if (connected && window.document.body.contains(loader)) {
+ window.document.body.removeChild(loaderBackground);
+ window.document.body.removeChild(loaderText);
+ window.document.body.removeChild(loader);
+ }
+ }, 1000);
+ }
}
function disconnectFromRobot() {
- connection.hangup();
- connection.stop();
+ connection.hangup();
+ connection.stop();
}
window.onbeforeunload = () => {
- connection.hangup();
- connection.stop();
+ connection.hangup();
+ connection.stop();
};
window.onunload = () => {
- connection.hangup();
- connection.stop();
+ connection.hangup();
+ connection.stop();
};
diff --git a/src/pages/operator/tsx/layout_components/ButtonGrid.tsx b/src/pages/operator/tsx/layout_components/ButtonGrid.tsx
index b7198c4d..3bdfd5d6 100644
--- a/src/pages/operator/tsx/layout_components/ButtonGrid.tsx
+++ b/src/pages/operator/tsx/layout_components/ButtonGrid.tsx
@@ -1,121 +1,121 @@
import { buttonFunctionProvider } from "operator/tsx/index";
import {
- ButtonFunctions,
- ButtonPadButton,
- ButtonState,
+ ButtonFunctions,
+ ButtonPadButton,
+ ButtonState,
} from "../function_providers/ButtonFunctionProvider";
import {
- CustomizableComponentProps,
- isSelected,
+ CustomizableComponentProps,
+ isSelected,
} from "./CustomizableComponent";
import { className } from "shared/util";
import "operator/css/ButtonGrid.css";
const BUTTON_NAMES = [
- "Forward",
- "Backwards",
- "Turn Left",
- "Turn Right",
+ "Forward",
+ "Backwards",
+ "Turn Left",
+ "Turn Right",
- "Move Lift Up",
- "Move Lift Down",
- "Extend Arm",
- "Collapse Arm",
+ "Move Lift Up",
+ "Move Lift Down",
+ "Extend Arm",
+ "Collapse Arm",
- "Roll Left",
- "Roll Right",
- "Pitch Up",
- "Pitch Down",
- "Rotate Left",
- "Rotate Right",
+ "Roll Left",
+ "Roll Right",
+ "Pitch Up",
+ "Pitch Down",
+ "Rotate Left",
+ "Rotate Right",
- "Open Gripper",
- "Close Gripper",
+ "Open Gripper",
+ "Close Gripper",
];
const BUTTON_FUNCTIONS = [
- ButtonPadButton.BaseForward,
- ButtonPadButton.BaseReverse,
- ButtonPadButton.BaseRotateLeft,
- ButtonPadButton.BaseRotateRight,
- ButtonPadButton.ArmLift,
- ButtonPadButton.ArmLower,
- ButtonPadButton.ArmExtend,
- ButtonPadButton.ArmRetract,
- ButtonPadButton.WristRollLeft,
- ButtonPadButton.WristRollRight,
- ButtonPadButton.WristPitchUp,
- ButtonPadButton.WristPitchDown,
- ButtonPadButton.WristRotateIn,
- ButtonPadButton.WristRotateOut,
- ButtonPadButton.GripperOpen,
- ButtonPadButton.GripperClose,
+ ButtonPadButton.BaseForward,
+ ButtonPadButton.BaseReverse,
+ ButtonPadButton.BaseRotateLeft,
+ ButtonPadButton.BaseRotateRight,
+ ButtonPadButton.ArmLift,
+ ButtonPadButton.ArmLower,
+ ButtonPadButton.ArmExtend,
+ ButtonPadButton.ArmRetract,
+ ButtonPadButton.WristRollLeft,
+ ButtonPadButton.WristRollRight,
+ ButtonPadButton.WristPitchUp,
+ ButtonPadButton.WristPitchDown,
+ ButtonPadButton.WristRotateIn,
+ ButtonPadButton.WristRotateOut,
+ ButtonPadButton.GripperOpen,
+ ButtonPadButton.GripperClose,
];
const HEADER_NAMES = [
- "Basic Driving Controls",
- "Basic Arm Controls",
- "Wrist Controls",
- "Gripper Controls",
+ "Basic Driving Controls",
+ "Basic Arm Controls",
+ "Wrist Controls",
+ "Gripper Controls",
];
const BACKGROUND_COLORS: JSX.Element[] = [];
for (let i = 0; i < 4; i++) {
- BACKGROUND_COLORS.push(
- ,
- );
+ BACKGROUND_COLORS.push(
+ ,
+ );
}
export const ButtonGrid = (props: CustomizableComponentProps) => {
- const { customizing } = props.sharedState;
- const selected = isSelected(props);
- function handleSelect(event: React.MouseEvent) {
- event.stopPropagation();
- props.sharedState.onSelect(props.definition, props.path);
- }
- return (
-
- {BACKGROUND_COLORS}
- {HEADER_NAMES.map((headerName, idx) => (
-
- {headerName}
-
- ))}
- {BUTTON_NAMES.map((buttonName, idx) => {
- const buttonFunction = BUTTON_FUNCTIONS[idx];
- const buttonState: ButtonState =
- props.sharedState.buttonStateMap?.get(buttonFunction) ||
- ButtonState.Inactive;
- const functs: ButtonFunctions =
- buttonFunctionProvider.provideFunctions(buttonFunction);
- const clickProps = props.sharedState.customizing
- ? {}
- : {
- onMouseDown: functs.onClick,
- onMouseUp: functs.onRelease,
- onMouseLeave: functs.onLeave,
- };
- return (
-
- {buttonName}
-
- );
- })}
-
- );
+ const { customizing } = props.sharedState;
+ const selected = isSelected(props);
+ function handleSelect(event: React.MouseEvent) {
+ event.stopPropagation();
+ props.sharedState.onSelect(props.definition, props.path);
+ }
+ return (
+
+ {BACKGROUND_COLORS}
+ {HEADER_NAMES.map((headerName, idx) => (
+
+ {headerName}
+
+ ))}
+ {BUTTON_NAMES.map((buttonName, idx) => {
+ const buttonFunction = BUTTON_FUNCTIONS[idx];
+ const buttonState: ButtonState =
+ props.sharedState.buttonStateMap?.get(buttonFunction) ||
+ ButtonState.Inactive;
+ const functs: ButtonFunctions =
+ buttonFunctionProvider.provideFunctions(buttonFunction);
+ const clickProps = props.sharedState.customizing
+ ? {}
+ : {
+ onMouseDown: functs.onClick,
+ onMouseUp: functs.onRelease,
+ onMouseLeave: functs.onLeave,
+ };
+ return (
+
+ {buttonName}
+
+ );
+ })}
+
+ );
};
diff --git a/src/pages/operator/tsx/layout_components/ButtonPad.tsx b/src/pages/operator/tsx/layout_components/ButtonPad.tsx
index 0d710c39..7b66f75a 100644
--- a/src/pages/operator/tsx/layout_components/ButtonPad.tsx
+++ b/src/pages/operator/tsx/layout_components/ButtonPad.tsx
@@ -1,36 +1,36 @@
import React from "react";
import {
- CustomizableComponentProps,
- SharedState,
- isSelected,
+ CustomizableComponentProps,
+ SharedState,
+ isSelected,
} from "./CustomizableComponent";
import {
- ButtonPadDefinition,
- ButtonPadId,
- ButtonPadIdMobile,
+ ButtonPadDefinition,
+ ButtonPadId,
+ ButtonPadIdMobile,
} from "../utils/component_definitions";
import { className } from "shared/util";
import { buttonFunctionProvider } from "operator/tsx/index";
import {
- ButtonPadShape,
- getIcon,
- getPathsFromShape,
- SVG_RESOLUTION,
+ ButtonPadShape,
+ getIcon,
+ getPathsFromShape,
+ SVG_RESOLUTION,
} from "../utils/svg";
import {
- ButtonFunctions,
- ButtonPadButton,
- ButtonState,
+ ButtonFunctions,
+ ButtonPadButton,
+ ButtonState,
} from "../function_providers/ButtonFunctionProvider";
import { isMobile } from "react-device-detect";
import "operator/css/ButtonPad.css";
/** Properties for {@link ButtonPad} */
type ButtonPadProps = CustomizableComponentProps & {
- /* If the button pad is overlaid on a camera view. */
- overlay?: boolean;
- /* Aspect ratio of the button pad */
- aspectRatio?: number;
+ /* If the button pad is overlaid on a camera view. */
+ overlay?: boolean;
+ /* Aspect ratio of the button pad */
+ aspectRatio?: number;
};
/**
@@ -40,113 +40,118 @@ type ButtonPadProps = CustomizableComponentProps & {
* @param props {@link ButtonPadProps}
*/
export const ButtonPad = (props: ButtonPadProps) => {
- /** Reference to the SVG which makes up the button pad */
- const svgRef = React.useRef(null);
- /** List of path shapes for each button on the button pad */
- const definition = props.definition as ButtonPadDefinition;
- const id: ButtonPadId = definition.id;
- if (!id) throw Error("Undefined button pad ID at path " + props.path);
- const [shape, functions] = getShapeAndFunctionsFromId(definition.id);
- const [paths, iconPositions] = getPathsFromShape(shape, props.aspectRatio);
+ /** Reference to the SVG which makes up the button pad */
+ const svgRef = React.useRef(null);
+ /** List of path shapes for each button on the button pad */
+ const definition = props.definition as ButtonPadDefinition;
+ const id: ButtonPadId = definition.id;
+ if (!id) throw Error("Undefined button pad ID at path " + props.path);
+ const [shape, functions] = getShapeAndFunctionsFromId(definition.id);
+ const [paths, iconPositions] = getPathsFromShape(shape, props.aspectRatio);
- // Paths and functions should be the same length
- if (paths.length !== functions.length) {
- throw Error(
- `paths length: ${paths.length}, functions length: ${functions.length}`,
- );
- }
+ // Paths and functions should be the same length
+ if (paths.length !== functions.length) {
+ throw Error(
+ `paths length: ${paths.length}, functions length: ${functions.length}`,
+ );
+ }
- const { customizing } = props.sharedState;
- const { overlay } = props;
- const selected = isSelected(props);
+ const { customizing } = props.sharedState;
+ const { overlay } = props;
+ const selected = isSelected(props);
- /** Uses the paths and buttonsProps to create the buttons */
- function mapPaths(svgPath: string, i: number) {
- const buttonProps = {
- iconPosition: iconPositions[i],
- svgPath,
- funct: functions[i],
- sharedState: props.sharedState,
- };
- // Buttons will not function during customization mode
- return ;
- }
+ /** Uses the paths and buttonsProps to create the buttons */
+ function mapPaths(svgPath: string, i: number) {
+ const buttonProps = {
+ iconPosition: iconPositions[i],
+ svgPath,
+ funct: functions[i],
+ sharedState: props.sharedState,
+ };
+ // Buttons will not function during customization mode
+ return ;
+ }
- /** Callback when SVG is clicked during customize mode */
- const onSelect = (event: React.MouseEvent) => {
- // Make sure the container of the button pad doesn't get selected
- event.stopPropagation();
- props.sharedState.onSelect(props.definition, props.path);
- };
+ /** Callback when SVG is clicked during customize mode */
+ const onSelect = (event: React.MouseEvent) => {
+ // Make sure the container of the button pad doesn't get selected
+ event.stopPropagation();
+ props.sharedState.onSelect(props.definition, props.path);
+ };
- // In customizing state add onClick callback to button pad SVG element
- // note: if overlaid on a video stream, let the parent video stream handle the click
- const selectProp =
- customizing && !overlay
- ? {
- onClick: onSelect,
- }
- : {};
+ // In customizing state add onClick callback to button pad SVG element
+ // note: if overlaid on a video stream, let the parent video stream handle the click
+ const selectProp =
+ customizing && !overlay
+ ? {
+ onClick: onSelect,
+ }
+ : {};
- return (
-
- {/* {!overlay && !isMobile?
{id} : <>>} */}
-
- {paths.map(mapPaths)}
-
-
- );
+ return (
+
+ {/* {!overlay && !isMobile?
{id} : <>>} */}
+
+ {paths.map(mapPaths)}
+
+
+ );
};
/** Properties for a single button on a button pad */
export type SingleButtonProps = {
- svgPath: string;
- funct: ButtonPadButton;
- sharedState: SharedState;
- iconPosition: { x: number; y: number };
+ svgPath: string;
+ funct: ButtonPadButton;
+ sharedState: SharedState;
+ iconPosition: { x: number; y: number };
};
const SingleButton = (props: SingleButtonProps) => {
- const functs: ButtonFunctions = buttonFunctionProvider.provideFunctions(
- props.funct,
- );
- const clickProps = props.sharedState.customizing
- ? {}
- : {
- onPointerDown: functs.onClick,
- onPointerUp: functs.onRelease,
- onPointerLeave: functs.onLeave,
- };
- const buttonState: ButtonState =
- props.sharedState.buttonStateMap?.get(props.funct) || ButtonState.Inactive;
- const icon = getIcon(props.funct);
- const title = props.funct;
- const height = isMobile ? 75 : 85;
- const width = isMobile ? 75 : 85;
- const x = props.iconPosition.x - width / 2;
- const y = props.iconPosition.y - height / 2;
- return (
-
-
- {title}
-
-
- {title}
-
- );
+ const functs: ButtonFunctions = buttonFunctionProvider.provideFunctions(
+ props.funct,
+ );
+ const clickProps = props.sharedState.customizing
+ ? {}
+ : {
+ onPointerDown: functs.onClick,
+ onPointerUp: functs.onRelease,
+ onPointerLeave: functs.onLeave,
+ };
+ const buttonState: ButtonState =
+ props.sharedState.buttonStateMap?.get(props.funct) ||
+ ButtonState.Inactive;
+ const icon = getIcon(props.funct);
+ const title = props.funct;
+ const height = isMobile ? 75 : 85;
+ const width = isMobile ? 75 : 85;
+ const x = props.iconPosition.x - width / 2;
+ const y = props.iconPosition.y - height / 2;
+ return (
+
+
+ {title}
+
+
+ {title}
+
+ );
};
/**
@@ -158,120 +163,120 @@ const SingleButton = (props: SingleButtonProps) => {
* the corresponding button on the button pad
*/
function getShapeAndFunctionsFromId(
- id: ButtonPadId | ButtonPadIdMobile,
+ id: ButtonPadId | ButtonPadIdMobile,
): [ButtonPadShape, ButtonPadButton[]] {
- let shape: ButtonPadShape;
- let functions: ButtonPadButton[];
- const B = ButtonPadButton;
- switch (id) {
- // case ButtonPadId.Drive:
- // functions = [
- // B.BaseForward,
- // B.BaseRotateRight,
- // B.BaseReverse,
- // B.BaseRotateLeft
- // ];
- // shape = ButtonPadShape.Directional;
- // break;
- case ButtonPadId.ManipRealsense:
- functions = [
- B.WristRotateIn,
- B.WristRotateOut,
- B.ArmExtend,
- B.ArmRetract,
- B.BaseForward,
- B.BaseReverse,
- B.ArmLift,
- B.ArmLower,
- B.GripperClose,
- B.GripperOpen,
- ];
- shape = ButtonPadShape.ManipRealsense;
- break;
- case ButtonPadId.GripperLift:
- functions = [
- B.ArmLift,
- B.ArmLower,
- B.WristRotateIn,
- B.WristRotateOut,
- B.GripperOpen,
- B.GripperClose,
- ];
- shape = ButtonPadShape.GripperLift;
- break;
- case ButtonPadId.DexWrist:
- functions = [
- B.WristPitchUp,
- B.WristPitchDown,
- B.WristRotateIn,
- B.WristRotateOut,
- B.WristRollLeft,
- B.WristRollRight,
- B.GripperOpen,
- B.GripperClose,
- ];
- shape = ButtonPadShape.DexWrist;
- break;
- case ButtonPadId.Base:
- functions = [
- B.BaseForward,
- B.BaseReverse,
- B.BaseRotateLeft,
- B.BaseRotateRight,
- ];
- shape = ButtonPadShape.SimpleButtonPad;
- break;
- case ButtonPadId.Camera:
- functions = [
- B.CameraTiltUp,
- B.CameraTiltDown,
- B.CameraPanLeft,
- B.CameraPanRight,
- ];
- shape = ButtonPadShape.SimpleButtonPad;
- break;
- // case ButtonPadId.Wrist:
- // functions = [
- // B.WristRollLeft,
- // B.WristRollRight,
- // B.WristPitchUp,
- // B.WristPitchDown,
- // B.WristRotateIn,
- // B.WristRotateOut,
- // B.GripperOpen,
- // B.GripperClose
- // ];
- // shape = ButtonPadShape.StackedButtonPad;
- // break;
- case ButtonPadId.Arm:
- functions = [B.ArmLift, B.ArmLower, B.ArmRetract, B.ArmExtend];
- shape = ButtonPadShape.SimpleButtonPad;
- break;
- case ButtonPadIdMobile.Arm:
- functions = [B.ArmLift, B.ArmLower, B.ArmRetract, B.ArmExtend];
- shape = ButtonPadShape.RowButtonPad;
- break;
- case ButtonPadIdMobile.Gripper:
- functions = [
- B.WristRotateIn,
- B.WristRotateOut,
- B.GripperOpen,
- B.GripperClose,
- ];
- shape = ButtonPadShape.RowButtonPad;
- break;
- case ButtonPadIdMobile.Drive:
- functions = [
- B.BaseForward,
- B.BaseReverse,
- B.BaseRotateLeft,
- B.BaseRotateRight,
- ];
- shape = ButtonPadShape.RowButtonPad;
- break;
- default:
- throw new Error(`unknow button pad id: ${id}`);
- }
+ let shape: ButtonPadShape;
+ let functions: ButtonPadButton[];
+ const B = ButtonPadButton;
+ switch (id) {
+ // case ButtonPadId.Drive:
+ // functions = [
+ // B.BaseForward,
+ // B.BaseRotateRight,
+ // B.BaseReverse,
+ // B.BaseRotateLeft
+ // ];
+ // shape = ButtonPadShape.Directional;
+ // break;
+ case ButtonPadId.ManipRealsense:
+ functions = [
+ B.WristRotateIn,
+ B.WristRotateOut,
+ B.ArmExtend,
+ B.ArmRetract,
+ B.BaseForward,
+ B.BaseReverse,
+ B.ArmLift,
+ B.ArmLower,
+ B.GripperClose,
+ B.GripperOpen,
+ ];
+ shape = ButtonPadShape.ManipRealsense;
+ break;
+ case ButtonPadId.GripperLift:
+ functions = [
+ B.ArmLift,
+ B.ArmLower,
+ B.WristRotateIn,
+ B.WristRotateOut,
+ B.GripperOpen,
+ B.GripperClose,
+ ];
+ shape = ButtonPadShape.GripperLift;
+ break;
+ case ButtonPadId.DexWrist:
+ functions = [
+ B.WristPitchUp,
+ B.WristPitchDown,
+ B.WristRotateIn,
+ B.WristRotateOut,
+ B.WristRollLeft,
+ B.WristRollRight,
+ B.GripperOpen,
+ B.GripperClose,
+ ];
+ shape = ButtonPadShape.DexWrist;
+ break;
+ case ButtonPadId.Base:
+ functions = [
+ B.BaseForward,
+ B.BaseReverse,
+ B.BaseRotateLeft,
+ B.BaseRotateRight,
+ ];
+ shape = ButtonPadShape.SimpleButtonPad;
+ break;
+ case ButtonPadId.Camera:
+ functions = [
+ B.CameraTiltUp,
+ B.CameraTiltDown,
+ B.CameraPanLeft,
+ B.CameraPanRight,
+ ];
+ shape = ButtonPadShape.SimpleButtonPad;
+ break;
+ // case ButtonPadId.Wrist:
+ // functions = [
+ // B.WristRollLeft,
+ // B.WristRollRight,
+ // B.WristPitchUp,
+ // B.WristPitchDown,
+ // B.WristRotateIn,
+ // B.WristRotateOut,
+ // B.GripperOpen,
+ // B.GripperClose
+ // ];
+ // shape = ButtonPadShape.StackedButtonPad;
+ // break;
+ case ButtonPadId.Arm:
+ functions = [B.ArmLift, B.ArmLower, B.ArmRetract, B.ArmExtend];
+ shape = ButtonPadShape.SimpleButtonPad;
+ break;
+ case ButtonPadIdMobile.Arm:
+ functions = [B.ArmLift, B.ArmLower, B.ArmRetract, B.ArmExtend];
+ shape = ButtonPadShape.RowButtonPad;
+ break;
+ case ButtonPadIdMobile.Gripper:
+ functions = [
+ B.WristRotateIn,
+ B.WristRotateOut,
+ B.GripperOpen,
+ B.GripperClose,
+ ];
+ shape = ButtonPadShape.RowButtonPad;
+ break;
+ case ButtonPadIdMobile.Drive:
+ functions = [
+ B.BaseForward,
+ B.BaseReverse,
+ B.BaseRotateLeft,
+ B.BaseRotateRight,
+ ];
+ shape = ButtonPadShape.RowButtonPad;
+ break;
+ default:
+ throw new Error(`unknow button pad id: ${id}`);
+ }
- return [shape, functions];
+ return [shape, functions];
}
diff --git a/src/pages/operator/tsx/layout_components/CameraView.tsx b/src/pages/operator/tsx/layout_components/CameraView.tsx
index ddc8f538..53ec973a 100644
--- a/src/pages/operator/tsx/layout_components/CameraView.tsx
+++ b/src/pages/operator/tsx/layout_components/CameraView.tsx
@@ -1,47 +1,47 @@
import React from "react";
import {
- className,
- gripperProps,
- navigationProps,
- realsenseProps,
- RemoteStream,
- StretchTool,
- TabletOrientation,
+ className,
+ gripperProps,
+ navigationProps,
+ realsenseProps,
+ RemoteStream,
+ StretchTool,
+ TabletOrientation,
} from "../../../../shared/util";
import {
- CameraViewDefinition,
- ComponentType,
- CameraViewId,
- ComponentDefinition,
- FixedOverheadVideoStreamDef,
- RealsenseVideoStreamDef,
- AdjustableOverheadVideoStreamDef,
- GripperVideoStreamDef,
+ CameraViewDefinition,
+ ComponentType,
+ CameraViewId,
+ ComponentDefinition,
+ FixedOverheadVideoStreamDef,
+ RealsenseVideoStreamDef,
+ AdjustableOverheadVideoStreamDef,
+ GripperVideoStreamDef,
} from "../utils/component_definitions";
import { ButtonPad } from "./ButtonPad";
import {
- CustomizableComponentProps,
- isSelected,
- SharedState,
+ CustomizableComponentProps,
+ isSelected,
+ SharedState,
} from "./CustomizableComponent";
import { DropZone } from "./DropZone";
import { PredictiveDisplay } from "./PredictiveDisplay";
import {
- buttonFunctionProvider,
- hasBetaTeleopKit,
- underVideoFunctionProvider,
+ buttonFunctionProvider,
+ hasBetaTeleopKit,
+ underVideoFunctionProvider,
} from "..";
import {
- ButtonPadButton,
- panTiltButtons,
+ ButtonPadButton,
+ panTiltButtons,
} from "../function_providers/ButtonFunctionProvider";
import {
- OverheadButtons,
- realsenseButtons,
- realsenseMoveToPregraspButtons,
- RealsenseButtons,
- UnderVideoButton,
- wristButtons,
+ OverheadButtons,
+ realsenseButtons,
+ realsenseMoveToPregraspButtons,
+ RealsenseButtons,
+ UnderVideoButton,
+ wristButtons,
} from "../function_providers/UnderVideoFunctionProvider";
import { CheckToggleButton } from "../basic_components/CheckToggleButton";
import { AccordionSelect } from "../basic_components/AccordionSelect";
@@ -53,290 +53,292 @@ import "operator/css/CameraView.css";
* @param props properties
*/
export const CameraView = (props: CustomizableComponentProps) => {
- // Reference to the video element
- const videoRef = React.useRef(null);
- // X and Y position of the cursor when user clicks on the video while customizing
- const [contextMenuXY, setContextMenuXY] = React.useState<
- [number, number] | null
- >(null);
- // Scaled x and y positions (in [0.0, 1.0]) when the user clicks the realsense stream to select an object
- const [selectObjectScaledXY, setSelectObjectScaledXY] = React.useState<
- [number, number] | null
- >(null);
- // The font size to use for the icon indicating the selected object
- const [selectObjectFontSize, setSelectObjectFontSize] =
- React.useState("1.5em");
- // Whether the robot is currently in the process of moving to a pregrasp position
- const [isMovingToPregrasp, setIsMovingToPregrasp] =
- React.useState(false);
- const definition = React.useMemo(
- () => props.definition as CameraViewDefinition,
- [props.definition],
- );
- if (!definition.children)
- console.warn(
- `Video stream definition at ${props.path} should have a 'children' property.`,
+ // Reference to the video element
+ const videoRef = React.useRef(null);
+ // X and Y position of the cursor when user clicks on the video while customizing
+ const [contextMenuXY, setContextMenuXY] = React.useState<
+ [number, number] | null
+ >(null);
+ // Scaled x and y positions (in [0.0, 1.0]) when the user clicks the realsense stream to select an object
+ const [selectObjectScaledXY, setSelectObjectScaledXY] = React.useState<
+ [number, number] | null
+ >(null);
+ // The font size to use for the icon indicating the selected object
+ const [selectObjectFontSize, setSelectObjectFontSize] =
+ React.useState("1.5em");
+ // Whether the robot is currently in the process of moving to a pregrasp position
+ const [isMovingToPregrasp, setIsMovingToPregrasp] =
+ React.useState(false);
+ const definition = React.useMemo(
+ () => props.definition as CameraViewDefinition,
+ [props.definition],
+ );
+ if (!definition.children)
+ console.warn(
+ `Video stream definition at ${props.path} should have a 'children' property.`,
+ );
+ // Reference to the div immediately around the video element
+ const videoAreaRef = React.useRef(null);
+ // Reference to the div below the video element
+ const underVideoAreaRef = React.useRef(null);
+ // Boolean representing if the video stream needs to be constrained by height
+ // (constrained by width otherwise)
+ const [constrainedHeight, setConstrainedHeight] =
+ React.useState(false);
+ // State that is specific to certain camera views
+ const [predictiveDisplay, setPredictiveDisplay] =
+ React.useState(false);
+ // Get the stream to display inside the video
+ const stream: MediaStream = React.useMemo(
+ () => getStream(definition.id, props.sharedState.remoteStreams),
+ [definition],
);
- // Reference to the div immediately around the video element
- const videoAreaRef = React.useRef(null);
- // Reference to the div below the video element
- const underVideoAreaRef = React.useRef(null);
- // Boolean representing if the video stream needs to be constrained by height
- // (constrained by width otherwise)
- const [constrainedHeight, setConstrainedHeight] =
- React.useState(false);
- // State that is specific to certain camera views
- const [predictiveDisplay, setPredictiveDisplay] =
- React.useState(false);
- // Get the stream to display inside the video
- const stream: MediaStream = React.useMemo(
- () => getStream(definition.id, props.sharedState.remoteStreams),
- [definition],
- );
- React.useEffect(() => {
- executeVideoSettings(definition);
- }, [definition]);
+ React.useEffect(() => {
+ executeVideoSettings(definition);
+ }, [definition]);
- // Create the overlay
- const overlayDefinition = predictiveDisplay
- ? { type: ComponentType.PredictiveDisplay }
- : definition.children && definition.children.length > 0
- ? definition.children[0]
- : undefined;
- const videoAspectRatio = getVideoAspectRatio(definition);
- const overlay = createOverlay(
- overlayDefinition,
- props.path,
- props.sharedState,
- videoAspectRatio,
- );
+ // Create the overlay
+ const overlayDefinition = predictiveDisplay
+ ? { type: ComponentType.PredictiveDisplay }
+ : definition.children && definition.children.length > 0
+ ? definition.children[0]
+ : undefined;
+ const videoAspectRatio = getVideoAspectRatio(definition);
+ const overlay = createOverlay(
+ overlayDefinition,
+ props.path,
+ props.sharedState,
+ videoAspectRatio,
+ );
- // Update the source of the video stream
- React.useEffect(() => {
- if (!videoRef?.current) return;
- videoRef.current.srcObject = stream;
- }, [stream]);
+ // Update the source of the video stream
+ React.useEffect(() => {
+ if (!videoRef?.current) return;
+ videoRef.current.srcObject = stream;
+ }, [stream]);
- const { customizing } = props.sharedState;
- const selected = isSelected(props);
- const videoClass = className("video-canvas", { customizing, selected });
- const realsense = props.definition.id === CameraViewId.realsense;
- const overhead = props.definition.id === CameraViewId.overhead;
+ const { customizing } = props.sharedState;
+ const selected = isSelected(props);
+ const videoClass = className("video-canvas", { customizing, selected });
+ const realsense = props.definition.id === CameraViewId.realsense;
+ const overhead = props.definition.id === CameraViewId.overhead;
- /** Mark this video stream as selected */
- function selectSelf() {
- props.sharedState.onSelect(props.definition, props.path);
- setContextMenuXY(null);
- }
+ /** Mark this video stream as selected */
+ function selectSelf() {
+ props.sharedState.onSelect(props.definition, props.path);
+ setContextMenuXY(null);
+ }
- /** Mark the button pad child as selected */
- function selectChild() {
- props.sharedState.onSelect(overlayDefinition!, props.path + "-0");
- setContextMenuXY(null);
- }
+ /** Mark the button pad child as selected */
+ function selectChild() {
+ props.sharedState.onSelect(overlayDefinition!, props.path + "-0");
+ setContextMenuXY(null);
+ }
- /** Opens a popup */
- function handleClick(event: React.MouseEvent) {
- event.stopPropagation();
+ /** Opens a popup */
+ function handleClick(event: React.MouseEvent) {
+ event.stopPropagation();
- // Get the coordinates of the click within the camera view
- const { clientX, clientY } = event;
- const { left, top, right, bottom } =
- videoRef.current!.getBoundingClientRect();
- const x = clientX - left;
- const y = clientY - top;
+ // Get the coordinates of the click within the camera view
+ const { clientX, clientY } = event;
+ const { left, top, right, bottom } =
+ videoRef.current!.getBoundingClientRect();
+ const x = clientX - left;
+ const y = clientY - top;
- console.log("CameraView clicked at x", x, "y ", y);
+ console.log("CameraView clicked at x", x, "y ", y);
- // If you are customizing and there is a button pad on top of the
- // video feed, display a dropdown letting users to specify which to
- // select. Note that even clicks on overlaid components will trigger
- // this (in addition to the overlaid component's click event).
- if (customizing) {
- // If no button pad overlay then select self and return
- if (
- !overlayDefinition ||
- overlayDefinition.type !== ComponentType.ButtonPad
- ) {
- selectSelf();
- return;
- }
+ // If you are customizing and there is a button pad on top of the
+ // video feed, display a dropdown letting users to specify which to
+ // select. Note that even clicks on overlaid components will trigger
+ // this (in addition to the overlaid component's click event).
+ if (customizing) {
+ // If no button pad overlay then select self and return
+ if (
+ !overlayDefinition ||
+ overlayDefinition.type !== ComponentType.ButtonPad
+ ) {
+ selectSelf();
+ return;
+ }
- // Create context menu popup where user can choose between selecting
- // the button pad or the video stream
- setContextMenuXY([x, y]);
- }
- // If it is the Realsense, there is no overlay (e.g., button pad),
- // and click-to-pregrasp is toggled on, select the goal.
- else if (
- props.definition.id === CameraViewId.realsense &&
- !overlay &&
- (props.definition as RealsenseVideoStreamDef)
- .selectObjectForMoveToPregrasp
- ) {
- let scaled_x = x / (right - left);
- let scaled_y = y / (bottom - top);
- setSelectObjectScaledXY([scaled_x, scaled_y]);
- console.log("scaled x", scaled_x, "scaled y", scaled_y);
- // underVideoFunctionProvider.provideFunctions(
- // UnderVideoButton.MoveToPregrasp,
- // ).onClick!(scaled_x, scaled_y);
+ // Create context menu popup where user can choose between selecting
+ // the button pad or the video stream
+ setContextMenuXY([x, y]);
+ }
+ // If it is the Realsense, there is no overlay (e.g., button pad),
+ // and click-to-pregrasp is toggled on, select the goal.
+ else if (
+ props.definition.id === CameraViewId.realsense &&
+ !overlay &&
+ (props.definition as RealsenseVideoStreamDef)
+ .selectObjectForMoveToPregrasp
+ ) {
+ let scaled_x = x / (right - left);
+ let scaled_y = y / (bottom - top);
+ setSelectObjectScaledXY([scaled_x, scaled_y]);
+ console.log("scaled x", scaled_x, "scaled y", scaled_y);
+ // underVideoFunctionProvider.provideFunctions(
+ // UnderVideoButton.MoveToPregrasp,
+ // ).onClick!(scaled_x, scaled_y);
+ }
}
- }
- // Constrain the width or height when the stream gets too large
- React.useEffect(() => {
- const resizeObserver = new ResizeObserver((entries) => {
- // height and width of area around the video stream
- const { height, width } = entries[0].contentRect;
- const areaAspectRatio = +(width / height).toFixed(2);
+ // Constrain the width or height when the stream gets too large
+ React.useEffect(() => {
+ const resizeObserver = new ResizeObserver((entries) => {
+ // height and width of area around the video stream
+ const { height, width } = entries[0].contentRect;
+ const areaAspectRatio = +(width / height).toFixed(2);
- // height and width of video stream
- if (!videoRef?.current) return;
- const videoRect = videoRef.current.getBoundingClientRect();
- const videoAspectRatio = +(videoRect.width / videoRect.height).toFixed(2);
+ // height and width of video stream
+ if (!videoRef?.current) return;
+ const videoRect = videoRef.current.getBoundingClientRect();
+ const videoAspectRatio = +(
+ videoRect.width / videoRect.height
+ ).toFixed(2);
- // Set whether the height or width is the constraining factor
- if (areaAspectRatio > videoAspectRatio) {
- setConstrainedHeight(true);
- } else if (areaAspectRatio < videoAspectRatio) {
- setConstrainedHeight(false);
- }
+ // Set whether the height or width is the constraining factor
+ if (areaAspectRatio > videoAspectRatio) {
+ setConstrainedHeight(true);
+ } else if (areaAspectRatio < videoAspectRatio) {
+ setConstrainedHeight(false);
+ }
- // Set the font size for the selected object icon
- setSelectObjectFontSize((videoRect.width / 8).toString() + "px");
- });
- if (!videoAreaRef?.current) return;
- resizeObserver.observe(videoAreaRef.current);
- return () => resizeObserver.disconnect();
- }, []);
+ // Set the font size for the selected object icon
+ setSelectObjectFontSize((videoRect.width / 8).toString() + "px");
+ });
+ if (!videoAreaRef?.current) return;
+ resizeObserver.observe(videoAreaRef.current);
+ return () => resizeObserver.disconnect();
+ }, []);
- const overlayContainer = (
-
- {
- // Display overlay on top of video stream
- overlay ? (
- overlay
- ) : (
-
- )
- }
- {
- // When contextMenuXY is set, display thr context menu
- contextMenuXY ? (
- setContextMenuXY(null)}
- />
- ) : undefined
- }
-
- );
- // If the video is from the Realsense camera then include the pan-tilt
- // buttons around the video, otherwise return the video
- let videoOverlay = <>>;
- if (props.definition.id === CameraViewId.realsense) {
- videoOverlay = (
- <>
+ const overlayContainer = (
- {panTiltButtons.map((dir) => (
-
- ))}
+ {
+ // Display overlay on top of video stream
+ overlay ? (
+ overlay
+ ) : (
+
+ )
+ }
+ {
+ // When contextMenuXY is set, display thr context menu
+ contextMenuXY ? (
+
setContextMenuXY(null)}
+ />
+ ) : undefined
+ }
- >
- );
- } else if (props.definition.id == CameraViewId.overhead) {
- videoOverlay = (
- <>
- {overlayDefinition?.type !== ComponentType.PredictiveDisplay &&
- !props.sharedState.hasBetaTeleopKit ? (
-
- {panTiltButtons.map((dir) => (
-
- ))}
-
- ) : (
- <>>
- )}
- >
);
- }
+ // If the video is from the Realsense camera then include the pan-tilt
+ // buttons around the video, otherwise return the video
+ let videoOverlay = <>>;
+ if (props.definition.id === CameraViewId.realsense) {
+ videoOverlay = (
+ <>
+
+ {panTiltButtons.map((dir) => (
+
+ ))}
+
+ >
+ );
+ } else if (props.definition.id == CameraViewId.overhead) {
+ videoOverlay = (
+ <>
+ {overlayDefinition?.type !== ComponentType.PredictiveDisplay &&
+ !props.sharedState.hasBetaTeleopKit ? (
+
+ {panTiltButtons.map((dir) => (
+
+ ))}
+
+ ) : (
+ <>>
+ )}
+ >
+ );
+ }
- const videoComponent = (
-
- {videoOverlay}
-
- {overlayContainer}
- {selectObjectScaledXY ? (
-
- add
-
- ) : undefined}
-
- );
- return (
-
- {videoComponent}
- {definition.displayButtons ? (
-
-
+ {videoOverlay}
+
+ {overlayContainer}
+ {selectObjectScaledXY ? (
+
+ add
+
+ ) : undefined}
+
+ );
+ return (
+
+ {videoComponent}
+ {definition.displayButtons ? (
+
+
+
+ ) : (
+ <>>
+ )}
- ) : (
- <>>
- )}
-
- );
+ );
};
/**
@@ -345,48 +347,48 @@ export const CameraView = (props: CustomizableComponentProps) => {
* @param props the direction of the button {@link PanTiltButton}
*/
const PanTiltButton = (props: { direction: ButtonPadButton }) => {
- let gridPosition: { gridRow: number; gridColumn: number }; // the position in the 3x3 grid around the video element
- let rotation: string; // how to rotate the arrow icon to point in the correct direction
- const functs = buttonFunctionProvider.provideFunctions(props.direction);
+ let gridPosition: { gridRow: number; gridColumn: number }; // the position in the 3x3 grid around the video element
+ let rotation: string; // how to rotate the arrow icon to point in the correct direction
+ const functs = buttonFunctionProvider.provideFunctions(props.direction);
- // Specify button details based on the direction
- switch (props.direction) {
- case ButtonPadButton.CameraTiltUp:
- gridPosition = { gridRow: 1, gridColumn: 2 };
- rotation = "-90";
- break;
- case ButtonPadButton.CameraTiltDown:
- gridPosition = { gridRow: 3, gridColumn: 2 };
- rotation = "90";
- break;
- case ButtonPadButton.CameraPanLeft:
- gridPosition = { gridRow: 2, gridColumn: 1 };
- rotation = "180";
- break;
- case ButtonPadButton.CameraPanRight:
- gridPosition = { gridRow: 2, gridColumn: 3 };
- rotation = "0"; // by default the arrow icon points right
- break;
- default:
- throw Error(`unknown pan tilt button direction ${props.direction}`);
- }
+ // Specify button details based on the direction
+ switch (props.direction) {
+ case ButtonPadButton.CameraTiltUp:
+ gridPosition = { gridRow: 1, gridColumn: 2 };
+ rotation = "-90";
+ break;
+ case ButtonPadButton.CameraTiltDown:
+ gridPosition = { gridRow: 3, gridColumn: 2 };
+ rotation = "90";
+ break;
+ case ButtonPadButton.CameraPanLeft:
+ gridPosition = { gridRow: 2, gridColumn: 1 };
+ rotation = "180";
+ break;
+ case ButtonPadButton.CameraPanRight:
+ gridPosition = { gridRow: 2, gridColumn: 3 };
+ rotation = "0"; // by default the arrow icon points right
+ break;
+ default:
+ throw Error(`unknown pan tilt button direction ${props.direction}`);
+ }
- return (
-
-
- arrow_right
-
-
- );
+ return (
+
+
+ arrow_right
+
+
+ );
};
/**
@@ -395,44 +397,44 @@ const PanTiltButton = (props: { direction: ButtonPadButton }) => {
* @param props the direction of the button {@link PanTiltButton}
*/
const PanTiltButtonOverlay = (props: { direction: ButtonPadButton }) => {
- const functs = buttonFunctionProvider.provideFunctions(props.direction);
- const dir = props.direction.split(" ")[2];
- let rotation: string;
+ const functs = buttonFunctionProvider.provideFunctions(props.direction);
+ const dir = props.direction.split(" ")[2];
+ let rotation: string;
- // Specify button details based on the direction
- switch (props.direction) {
- case ButtonPadButton.CameraTiltUp:
- rotation = "-90";
- break;
- case ButtonPadButton.CameraTiltDown:
- rotation = "90";
- break;
- case ButtonPadButton.CameraPanLeft:
- rotation = "180";
- break;
- case ButtonPadButton.CameraPanRight:
- rotation = "0"; // by default the arrow icon points right
- break;
- default:
- throw Error(`unknown pan tilt button direction ${props.direction}`);
- }
+ // Specify button details based on the direction
+ switch (props.direction) {
+ case ButtonPadButton.CameraTiltUp:
+ rotation = "-90";
+ break;
+ case ButtonPadButton.CameraTiltDown:
+ rotation = "90";
+ break;
+ case ButtonPadButton.CameraPanLeft:
+ rotation = "180";
+ break;
+ case ButtonPadButton.CameraPanRight:
+ rotation = "0"; // by default the arrow icon points right
+ break;
+ default:
+ throw Error(`unknown pan tilt button direction ${props.direction}`);
+ }
- return (
-
- {/* */}
-
- arrow_right
-
-
- );
+ return (
+
+ {/* */}
+
+ arrow_right
+
+
+ );
};
/******************************************************************************
@@ -441,14 +443,14 @@ const PanTiltButtonOverlay = (props: { direction: ButtonPadButton }) => {
/** Props for {@link SelectContexMenu} */
type SelectContexMenuProps = {
- /** x and y location to render the context menu popup */
- clickXY: [number, number];
- /** Callback to select the video stream */
- selectSelf: () => void;
- /** Callback to select the child button pad */
- selectChild: () => void;
- /** Callback to hide the context menu popup when click outside */
- clickOut: () => void;
+ /** x and y location to render the context menu popup */
+ clickXY: [number, number];
+ /** Callback to select the video stream */
+ selectSelf: () => void;
+ /** Callback to select the child button pad */
+ selectChild: () => void;
+ /** Callback to hide the context menu popup when click outside */
+ clickOut: () => void;
};
/**
@@ -459,50 +461,50 @@ type SelectContexMenuProps = {
* @param props {@link SelectContexMenuProps}
*/
const SelectContexMenu = (props: SelectContexMenuProps) => {
- const ref = React.useRef(null);
- const [x, y] = props.clickXY;
+ const ref = React.useRef(null);
+ const [x, y] = props.clickXY;
- // Handler to close dropdown when click outside
- React.useEffect(() => {
- /** Closes context menu if user clicks outside */
- const handler = (e: any) => {
- // If didn't click inside the context menu or the existing SVG, then
- // hide the popup
- if (ref.current && !ref.current.contains(e.target)) {
- props.clickOut();
- console.log("clicked");
- }
- };
- window.addEventListener("click", handler, true);
- return () => {
- window.removeEventListener("click", handler);
- };
- }, []);
+ // Handler to close dropdown when click outside
+ React.useEffect(() => {
+ /** Closes context menu if user clicks outside */
+ const handler = (e: any) => {
+ // If didn't click inside the context menu or the existing SVG, then
+ // hide the popup
+ if (ref.current && !ref.current.contains(e.target)) {
+ props.clickOut();
+ console.log("clicked");
+ }
+ };
+ window.addEventListener("click", handler, true);
+ return () => {
+ window.removeEventListener("click", handler);
+ };
+ }, []);
- /**
- * Handles when the user clicks on one of the context menu options
- * @param e mouse event of the click
- * @param self if true selects itself (a button pad), if false selects its
- * parent (the video stream)
- */
- function handleClick(e: React.MouseEvent, self: boolean) {
- self ? props.selectSelf() : props.selectChild();
+ /**
+ * Handles when the user clicks on one of the context menu options
+ * @param e mouse event of the click
+ * @param self if true selects itself (a button pad), if false selects its
+ * parent (the video stream)
+ */
+ function handleClick(e: React.MouseEvent, self: boolean) {
+ self ? props.selectSelf() : props.selectChild();
- // Make sure background elements don't receive a click
- e.stopPropagation();
- }
+ // Make sure background elements don't receive a click
+ e.stopPropagation();
+ }
- return (
-
- handleClick(e, false)}>Button Pad
- handleClick(e, true)}>Video Stream
-
- );
+ return (
+
+ handleClick(e, false)}>Button Pad
+ handleClick(e, true)}>Video Stream
+
+ );
};
/*******************************************************************************
@@ -516,16 +518,16 @@ const SelectContexMenu = (props: SelectContexMenuProps) => {
* @returns aspect ratio of the video stream
*/
function getVideoAspectRatio(definition: CameraViewDefinition): number {
- switch (definition.id) {
- case CameraViewId.gripper:
- return gripperProps.width / gripperProps.height;
- case CameraViewId.overhead:
- return navigationProps.width / navigationProps.height;
- case CameraViewId.realsense:
- return realsenseProps.width / realsenseProps.height;
- default:
- throw Error(`undefined aspect ratio for ${definition.type}`);
- }
+ switch (definition.id) {
+ case CameraViewId.gripper:
+ return gripperProps.width / gripperProps.height;
+ case CameraViewId.overhead:
+ return navigationProps.width / navigationProps.height;
+ case CameraViewId.realsense:
+ return realsenseProps.width / realsenseProps.height;
+ default:
+ throw Error(`undefined aspect ratio for ${definition.type}`);
+ }
}
/**
@@ -537,38 +539,46 @@ function getVideoAspectRatio(definition: CameraViewDefinition): number {
* @returns overlay element, or undefined if video stream doesn't have an overlay
*/
function createOverlay(
- overlayDefinition: ComponentDefinition | undefined,
- path: string,
- sharedState: SharedState,
- aspectRatio: number,
+ overlayDefinition: ComponentDefinition | undefined,
+ path: string,
+ sharedState: SharedState,
+ aspectRatio: number,
): JSX.Element | undefined {
- // If overlay definition is undefined then there's no overlay for this stream
- if (!overlayDefinition) return undefined;
- if (!overlayDefinition.type) {
- console.warn(`Video stream at path ${path} has child with undefined type:`);
- console.warn(overlayDefinition);
- return undefined;
- }
+ // If overlay definition is undefined then there's no overlay for this stream
+ if (!overlayDefinition) return undefined;
+ if (!overlayDefinition.type) {
+ console.warn(
+ `Video stream at path ${path} has child with undefined type:`,
+ );
+ console.warn(overlayDefinition);
+ return undefined;
+ }
- const overlayProps = {
- definition: overlayDefinition,
- path: path + "-0",
- sharedState: sharedState,
- } as CustomizableComponentProps;
+ const overlayProps = {
+ definition: overlayDefinition,
+ path: path + "-0",
+ sharedState: sharedState,
+ } as CustomizableComponentProps;
- switch (overlayDefinition.type) {
- case ComponentType.ButtonPad:
- return ;
- case ComponentType.PredictiveDisplay:
- return ;
- default:
- throw Error(
- "Video stream at path " +
- path +
- " cannot overlay child of type" +
- overlayDefinition.type,
- );
- }
+ switch (overlayDefinition.type) {
+ case ComponentType.ButtonPad:
+ return (
+
+ );
+ case ComponentType.PredictiveDisplay:
+ return ;
+ default:
+ throw Error(
+ "Video stream at path " +
+ path +
+ " cannot overlay child of type" +
+ overlayDefinition.type,
+ );
+ }
}
/**
@@ -579,24 +589,24 @@ function createOverlay(
* @returns the corresponding stream
*/
function getStream(
- id: CameraViewId,
- remoteStreams: Map,
+ id: CameraViewId,
+ remoteStreams: Map,
): MediaStream {
- let streamName: string;
- switch (id) {
- case CameraViewId.overhead:
- streamName = "overhead";
- break;
- case CameraViewId.realsense:
- streamName = "realsense";
- break;
- case CameraViewId.gripper:
- streamName = "gripper";
- break;
- default:
- throw Error(`unknown video stream id: ${id}`);
- }
- return remoteStreams.get(streamName)!.stream;
+ let streamName: string;
+ switch (id) {
+ case CameraViewId.overhead:
+ streamName = "overhead";
+ break;
+ case CameraViewId.realsense:
+ streamName = "realsense";
+ break;
+ case CameraViewId.gripper:
+ streamName = "gripper";
+ break;
+ default:
+ throw Error(`unknown video stream id: ${id}`);
+ }
+ return remoteStreams.get(streamName)!.stream;
}
/**
@@ -607,22 +617,22 @@ function getStream(
* @param definition {@link CameraViewDefinition}
*/
function executeVideoSettings(definition: CameraViewDefinition) {
- switch (definition.id) {
- case CameraViewId.gripper:
- executeGripperSettings(definition as GripperVideoStreamDef);
- break;
- case CameraViewId.overhead:
- // executeFixedOverheadSettings(definition as FixedOverheadVideoStreamDef);
- executeAdjustableOverheadettings(
- definition as AdjustableOverheadVideoStreamDef,
- );
- break;
- case CameraViewId.realsense:
- executeRealsenseSettings(definition as RealsenseVideoStreamDef);
- break;
- default:
- throw Error(`unknow video stream id: ${definition.id}`);
- }
+ switch (definition.id) {
+ case CameraViewId.gripper:
+ executeGripperSettings(definition as GripperVideoStreamDef);
+ break;
+ case CameraViewId.overhead:
+ // executeFixedOverheadSettings(definition as FixedOverheadVideoStreamDef);
+ executeAdjustableOverheadettings(
+ definition as AdjustableOverheadVideoStreamDef,
+ );
+ break;
+ case CameraViewId.realsense:
+ executeRealsenseSettings(definition as RealsenseVideoStreamDef);
+ break;
+ default:
+ throw Error(`unknow video stream id: ${definition.id}`);
+ }
}
/**
@@ -631,10 +641,10 @@ function executeVideoSettings(definition: CameraViewDefinition) {
* @param definition {@link FixedOverheadVideoStreamDef}
*/
function executeFixedOverheadSettings(definition: FixedOverheadVideoStreamDef) {
- const overheadViewButton = definition.gripperView
- ? UnderVideoButton.GripperView
- : UnderVideoButton.DriveView;
- underVideoFunctionProvider.provideFunctions(overheadViewButton).onClick!();
+ const overheadViewButton = definition.gripperView
+ ? UnderVideoButton.GripperView
+ : UnderVideoButton.DriveView;
+ underVideoFunctionProvider.provideFunctions(overheadViewButton).onClick!();
}
/**
@@ -643,10 +653,10 @@ function executeFixedOverheadSettings(definition: FixedOverheadVideoStreamDef) {
* @param definition {@link AdjustableOverheadVideoStreamDef}
*/
function executeAdjustableOverheadettings(
- definition: AdjustableOverheadVideoStreamDef,
+ definition: AdjustableOverheadVideoStreamDef,
) {
- underVideoFunctionProvider.provideFunctions(UnderVideoButton.FollowGripper)
- .onCheck!(definition.followGripper || false);
+ underVideoFunctionProvider.provideFunctions(UnderVideoButton.FollowGripper)
+ .onCheck!(definition.followGripper || false);
}
/**
@@ -655,11 +665,11 @@ function executeAdjustableOverheadettings(
* @param definition {@link RealsenseVideoStreamDef}
*/
function executeRealsenseSettings(definition: RealsenseVideoStreamDef) {
- underVideoFunctionProvider.provideFunctions(UnderVideoButton.FollowGripper)
- .onCheck!(definition.followGripper || false);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.RealsenseDepthSensing,
- ).onCheck!(definition.depthSensing || false);
+ underVideoFunctionProvider.provideFunctions(UnderVideoButton.FollowGripper)
+ .onCheck!(definition.followGripper || false);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.RealsenseDepthSensing,
+ ).onCheck!(definition.depthSensing || false);
}
/**
@@ -668,12 +678,12 @@ function executeRealsenseSettings(definition: RealsenseVideoStreamDef) {
* @param definition {@link GripperVideoStreamDef}
*/
function executeGripperSettings(definition: GripperVideoStreamDef) {
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.GripperDepthSensing,
- ).onCheck!(definition.depthSensing || false);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.ExpandedGripperView,
- ).onCheck!(definition.expandedGripperView || false);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.GripperDepthSensing,
+ ).onCheck!(definition.depthSensing || false);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.ExpandedGripperView,
+ ).onCheck!(definition.expandedGripperView || false);
}
/*******************************************************************************
@@ -685,280 +695,292 @@ function executeGripperSettings(definition: GripperVideoStreamDef) {
* stream, display depth sensing on Realsense, etc.)
*/
const UnderVideoButtons = (props: {
- definition: CameraViewDefinition;
- setPredictiveDisplay: (enabled: boolean) => void;
- selectObjectScaledXY: [number, number] | null;
- setSelectObjectScaledXY: (scaledXY: [number, number] | null) => void;
- isMovingToPregrasp: boolean;
- setIsMovingToPregrasp: (isMoving: boolean) => void;
- underVideoAreaRef: React.RefObject;
- betaTeleopKit: boolean;
- stretchTool: StretchTool;
+ definition: CameraViewDefinition;
+ setPredictiveDisplay: (enabled: boolean) => void;
+ selectObjectScaledXY: [number, number] | null;
+ setSelectObjectScaledXY: (scaledXY: [number, number] | null) => void;
+ isMovingToPregrasp: boolean;
+ setIsMovingToPregrasp: (isMoving: boolean) => void;
+ underVideoAreaRef: React.RefObject;
+ betaTeleopKit: boolean;
+ stretchTool: StretchTool;
}) => {
- let buttons: JSX.Element | null;
- switch (props.definition.id) {
- case CameraViewId.gripper:
- buttons = (
-
- );
- break;
- case CameraViewId.overhead:
- buttons = hasBetaTeleopKit ? (
-
- ) : (
-
- );
- break;
- case CameraViewId.realsense:
- buttons = (
-
- );
- break;
- default:
- throw Error(`unknow video stream id: ${props.definition.id}`);
- }
- return buttons;
+ let buttons: JSX.Element | null;
+ switch (props.definition.id) {
+ case CameraViewId.gripper:
+ buttons = (
+
+ );
+ break;
+ case CameraViewId.overhead:
+ buttons = hasBetaTeleopKit ? (
+
+ ) : (
+
+ );
+ break;
+ case CameraViewId.realsense:
+ buttons = (
+
+ );
+ break;
+ default:
+ throw Error(`unknow video stream id: ${props.definition.id}`);
+ }
+ return buttons;
};
/**
* Buttons to display under the overhead video stream.
*/
const UnderOverheadButtons = (props: {
- definition: FixedOverheadVideoStreamDef;
- setPredictiveDisplay: (enabled: boolean) => void;
+ definition: FixedOverheadVideoStreamDef;
+ setPredictiveDisplay: (enabled: boolean) => void;
}) => {
- const [rerender, setRerender] = React.useState(false);
+ const [rerender, setRerender] = React.useState(false);
- return (
-
- {
- if (!props.definition.predictiveDisplay) {
- props.definition.predictiveDisplay = true;
- props.setPredictiveDisplay(true);
- } else {
- props.definition.predictiveDisplay = false;
- props.setPredictiveDisplay(false);
- }
- setRerender(!rerender);
- }}
- label="Predictive Display"
- />
-
- );
+ return (
+
+ {
+ if (!props.definition.predictiveDisplay) {
+ props.definition.predictiveDisplay = true;
+ props.setPredictiveDisplay(true);
+ } else {
+ props.definition.predictiveDisplay = false;
+ props.setPredictiveDisplay(false);
+ }
+ setRerender(!rerender);
+ }}
+ label="Predictive Display"
+ />
+
+ );
};
/**
* Buttons to display under the adjustable overhead video stream.
*/
const UnderAdjustableOverheadButtons = (props: {
- definition: AdjustableOverheadVideoStreamDef;
- setPredictiveDisplay: (enabled: boolean) => void;
- stretchTool: StretchTool;
+ definition: AdjustableOverheadVideoStreamDef;
+ setPredictiveDisplay: (enabled: boolean) => void;
+ stretchTool: StretchTool;
}) => {
- const [rerender, setRerender] = React.useState(false);
+ const [rerender, setRerender] = React.useState(false);
- return (
-
- {
- if (button === UnderVideoButton.LookAtGripper) {
- return "Look at " + getGripperLabel(props.stretchTool);
- }
- return button;
- })}
- onChange={(idx: number) => {
- underVideoFunctionProvider.provideFunctions(realsenseButtons[idx])
- .onClick!();
- }}
- />
- {
- props.definition.followGripper = !props.definition.followGripper;
- setRerender(!rerender);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.FollowGripper,
- ).onCheck!(props.definition.followGripper);
- }}
- label={"Follow " + getGripperLabel(props.stretchTool)}
- />
- {
- if (!props.definition.predictiveDisplay) {
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.LookAtBase,
- ).onClick!();
- props.setPredictiveDisplay(true);
- props.definition.predictiveDisplay = true;
- } else {
- props.setPredictiveDisplay(false);
- props.definition.predictiveDisplay = false;
- }
- setRerender(!rerender);
- }}
- label="Predictive Display"
- />
-
- );
+ return (
+
+ {
+ if (button === UnderVideoButton.LookAtGripper) {
+ return "Look at " + getGripperLabel(props.stretchTool);
+ }
+ return button;
+ })}
+ onChange={(idx: number) => {
+ underVideoFunctionProvider.provideFunctions(
+ realsenseButtons[idx],
+ ).onClick!();
+ }}
+ />
+ {
+ props.definition.followGripper =
+ !props.definition.followGripper;
+ setRerender(!rerender);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.FollowGripper,
+ ).onCheck!(props.definition.followGripper);
+ }}
+ label={"Follow " + getGripperLabel(props.stretchTool)}
+ />
+ {
+ if (!props.definition.predictiveDisplay) {
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.LookAtBase,
+ ).onClick!();
+ props.setPredictiveDisplay(true);
+ props.definition.predictiveDisplay = true;
+ } else {
+ props.setPredictiveDisplay(false);
+ props.definition.predictiveDisplay = false;
+ }
+ setRerender(!rerender);
+ }}
+ label="Predictive Display"
+ />
+
+ );
};
/**
* Buttons to display under the Realsense video stream.
*/
const UnderRealsenseButtons = (props: {
- definition: RealsenseVideoStreamDef;
- selectObjectScaledXY: [number, number] | null;
- setSelectObjectScaledXY: (scaledXY: [number, number] | null) => void;
- isMovingToPregrasp: boolean;
- setIsMovingToPregrasp: (isMoving: boolean) => void;
- underVideoAreaRef: React.RefObject;
- stretchTool: StretchTool;
+ definition: RealsenseVideoStreamDef;
+ selectObjectScaledXY: [number, number] | null;
+ setSelectObjectScaledXY: (scaledXY: [number, number] | null) => void;
+ isMovingToPregrasp: boolean;
+ setIsMovingToPregrasp: (isMoving: boolean) => void;
+ underVideoAreaRef: React.RefObject;
+ stretchTool: StretchTool;
}) => {
- const [rerender, setRerender] = React.useState(false);
- const [selectedIdx, setSelectedIdx] = React.useState();
- // const [markers, setMarkers] = React.useState(['light_switch'])
+ const [rerender, setRerender] = React.useState(false);
+ const [selectedIdx, setSelectedIdx] = React.useState();
+ // const [markers, setMarkers] = React.useState(['light_switch'])
- console.log("UnderRealsenseButtons", props.isMovingToPregrasp);
+ console.log("UnderRealsenseButtons", props.isMovingToPregrasp);
- // Only show MoveToPregrasp buttons if the robot has a Dex wrist with a gripper
- let moveToPregraspButtons = <>>;
- if (props.stretchTool === StretchTool.DEX_GRIPPER) {
- moveToPregraspButtons = (
-
- {
- props.definition.selectObjectForMoveToPregrasp =
- !props.definition.selectObjectForMoveToPregrasp;
- props.setSelectObjectScaledXY(null);
- setRerender(!rerender);
- }}
- label="Select Object"
- />
- {props.isMovingToPregrasp ? (
- {
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.CancelMoveToPregrasp,
- ).onClick!();
- props.setIsMovingToPregrasp(false);
- }}
- >
- Cancel
- cancel
-
- ) : (
- {
- if (props.selectObjectScaledXY == null) {
- underVideoFunctionProvider.setMoveToPregraspState({
- state: "Please select an object first.",
- alert_type: "error",
- });
- return;
- }
- underVideoFunctionProvider.provideFunctions(
- realsenseMoveToPregraspButtons[idx],
- ).onClick!(props.selectObjectScaledXY);
- props.setSelectObjectScaledXY(null);
- props.setIsMovingToPregrasp(true);
- props.definition.selectObjectForMoveToPregrasp = false;
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.MoveToPregraspGoalReached,
- ).getFuture!().then(() => {
- props.setIsMovingToPregrasp(false);
- });
- }}
- toggleAccordianCallback={() => {
- if (props.underVideoAreaRef.current) {
- // Over the next 600ms, which is the CSS-specified transition time for
- // an accordion, keep scrolling the underVideoArea to the bottom, in case
- // the expanded accordion is off-screen.
- let startTime = Date.now();
- let scrollInterval = setInterval(() => {
- let currentTime = Date.now();
- let timeElapsed = currentTime - startTime;
- if (timeElapsed >= 600) {
- clearInterval(scrollInterval);
- } else {
- props.underVideoAreaRef.current!.scrollTop =
- props.underVideoAreaRef.current!.scrollHeight;
- }
- }, 10);
- }
- }}
- />
- )}
-
- );
- }
+ // Only show MoveToPregrasp buttons if the robot has a Dex wrist with a gripper
+ let moveToPregraspButtons = <>>;
+ if (props.stretchTool === StretchTool.DEX_GRIPPER) {
+ moveToPregraspButtons = (
+
+ {
+ props.definition.selectObjectForMoveToPregrasp =
+ !props.definition.selectObjectForMoveToPregrasp;
+ props.setSelectObjectScaledXY(null);
+ setRerender(!rerender);
+ }}
+ label="Select Object"
+ />
+ {props.isMovingToPregrasp ? (
+ {
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.CancelMoveToPregrasp,
+ ).onClick!();
+ props.setIsMovingToPregrasp(false);
+ }}
+ >
+ Cancel
+ cancel
+
+ ) : (
+ {
+ if (props.selectObjectScaledXY == null) {
+ underVideoFunctionProvider.setMoveToPregraspState(
+ {
+ state: "Please select an object first.",
+ alert_type: "error",
+ },
+ );
+ return;
+ }
+ underVideoFunctionProvider.provideFunctions(
+ realsenseMoveToPregraspButtons[idx],
+ ).onClick!(props.selectObjectScaledXY);
+ props.setSelectObjectScaledXY(null);
+ props.setIsMovingToPregrasp(true);
+ props.definition.selectObjectForMoveToPregrasp =
+ false;
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.MoveToPregraspGoalReached,
+ ).getFuture!().then(() => {
+ props.setIsMovingToPregrasp(false);
+ });
+ }}
+ toggleAccordianCallback={() => {
+ if (props.underVideoAreaRef.current) {
+ // Over the next 600ms, which is the CSS-specified transition time for
+ // an accordion, keep scrolling the underVideoArea to the bottom, in case
+ // the expanded accordion is off-screen.
+ let startTime = Date.now();
+ let scrollInterval = setInterval(() => {
+ let currentTime = Date.now();
+ let timeElapsed = currentTime - startTime;
+ if (timeElapsed >= 600) {
+ clearInterval(scrollInterval);
+ } else {
+ props.underVideoAreaRef.current!.scrollTop =
+ props.underVideoAreaRef.current!.scrollHeight;
+ }
+ }, 10);
+ }
+ }}
+ />
+ )}
+
+ );
+ }
- return (
-
- {
- if (button === UnderVideoButton.LookAtGripper) {
- return "Look at " + getGripperLabel(props.stretchTool);
- }
- return button;
- })}
- onChange={(idx: number) => {
- underVideoFunctionProvider.provideFunctions(realsenseButtons[idx])
- .onClick!();
- }}
- />
- {
- props.definition.followGripper = !props.definition.followGripper;
- setRerender(!rerender);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.FollowGripper,
- ).onCheck!(props.definition.followGripper);
- }}
- label={"Follow " + getGripperLabel(props.stretchTool)}
- />
- {
- props.definition.depthSensing = !props.definition.depthSensing;
- setRerender(!rerender);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.RealsenseDepthSensing,
- ).onCheck!(props.definition.depthSensing);
- }}
- label="Depth Sensing"
- />
- {moveToPregraspButtons}
- {/*
+ {
+ if (button === UnderVideoButton.LookAtGripper) {
+ return "Look at " + getGripperLabel(props.stretchTool);
+ }
+ return button;
+ })}
+ onChange={(idx: number) => {
+ underVideoFunctionProvider.provideFunctions(
+ realsenseButtons[idx],
+ ).onClick!();
+ }}
+ />
+ {
+ props.definition.followGripper =
+ !props.definition.followGripper;
+ setRerender(!rerender);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.FollowGripper,
+ ).onCheck!(props.definition.followGripper);
+ }}
+ label={"Follow " + getGripperLabel(props.stretchTool)}
+ />
+ {
+ props.definition.depthSensing =
+ !props.definition.depthSensing;
+ setRerender(!rerender);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.RealsenseDepthSensing,
+ ).onCheck!(props.definition.depthSensing);
+ }}
+ label="Depth Sensing"
+ />
+ {moveToPregraspButtons}
+ {/* {
props.definition.arucoMarkers = !props.definition.arucoMarkers;
@@ -967,7 +989,7 @@ const UnderRealsenseButtons = (props: {
}}
label="Aruco Markers"
/> */}
- {/*
*/}
-
- );
+
+ );
};
/**
* Buttons to display under the gripper video stream.
*/
const UnderGripperButtons = (props: {
- definition: GripperVideoStreamDef;
- betaTeleopKit: boolean;
- stretchTool: StretchTool;
+ definition: GripperVideoStreamDef;
+ betaTeleopKit: boolean;
+ stretchTool: StretchTool;
}) => {
- let tabletOrientation = underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.GetTabletOrientation,
- ).get!();
- const [rerender, setRerender] = React.useState(false);
+ let tabletOrientation = underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.GetTabletOrientation,
+ ).get!();
+ const [rerender, setRerender] = React.useState(false);
- return (
-
- {
- underVideoFunctionProvider.provideFunctions(wristButtons[idx])
- .onClick!();
- }}
- />
- {props.betaTeleopKit ? (
- <>>
- ) : (
+ return (
- {
- props.definition.expandedGripperView =
- !props.definition.expandedGripperView;
- setRerender(!rerender);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.ExpandedGripperView,
- ).onCheck!(props.definition.expandedGripperView!);
- }}
- label="Expanded Gripper View"
- />
- {(props.stretchTool === StretchTool.GRIPPER ||
- props.stretchTool === StretchTool.DEX_GRIPPER) && (
- {
- props.definition.depthSensing = !props.definition.depthSensing;
- setRerender(!rerender);
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.GripperDepthSensing,
- ).onCheck!(props.definition.depthSensing);
- }}
- label="Depth Sensing"
+ {
+ underVideoFunctionProvider.provideFunctions(
+ wristButtons[idx],
+ ).onClick!();
+ }}
/>
- )}
+ {props.betaTeleopKit ? (
+ <>>
+ ) : (
+
+ {
+ props.definition.expandedGripperView =
+ !props.definition.expandedGripperView;
+ setRerender(!rerender);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.ExpandedGripperView,
+ ).onCheck!(props.definition.expandedGripperView!);
+ }}
+ label="Expanded Gripper View"
+ />
+ {(props.stretchTool === StretchTool.GRIPPER ||
+ props.stretchTool === StretchTool.DEX_GRIPPER) && (
+ {
+ props.definition.depthSensing =
+ !props.definition.depthSensing;
+ setRerender(!rerender);
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.GripperDepthSensing,
+ ).onCheck!(props.definition.depthSensing);
+ }}
+ label="Depth Sensing"
+ />
+ )}
+
+ )}
+ {props.stretchTool === StretchTool.TABLET && (
+ {
+ let isPortait =
+ tabletOrientation === TabletOrientation.PORTRAIT;
+ underVideoFunctionProvider.provideFunctions(
+ UnderVideoButton.ToggleTabletOrientation,
+ ).onCheck!(isPortait);
+ setRerender(!rerender);
+ }}
+ >
+ Switch to{" "}
+ {tabletOrientation === TabletOrientation.PORTRAIT
+ ? "Landscape"
+ : "Portrait"}{" "}
+ View
+
+ )}
- )}
- {props.stretchTool === StretchTool.TABLET && (
- {
- let isPortait = tabletOrientation === TabletOrientation.PORTRAIT;
- underVideoFunctionProvider.provideFunctions(
- UnderVideoButton.ToggleTabletOrientation,
- ).onCheck!(isPortait);
- setRerender(!rerender);
- }}
- >
- Switch to{" "}
- {tabletOrientation === TabletOrientation.PORTRAIT
- ? "Landscape"
- : "Portrait"}{" "}
- View
-
- )}
-
- );
+ );
};
/**
* Button to change the camera perspective for a given video stream.
*/
const CameraPerspectiveButton = (props: {
- /**
- * When the button is clicked, the corresponding video stream should change
- * to this perspective.
- */
- perspective: OverheadButtons | RealsenseButtons;
+ /**
+ * When the button is clicked, the corresponding video stream should change
+ * to this perspective.
+ */
+ perspective: OverheadButtons | RealsenseButtons;
}) => {
- const onClick = underVideoFunctionProvider.provideFunctions(
- props.perspective,
- ).onClick;
- return {props.perspective} ;
+ const onClick = underVideoFunctionProvider.provideFunctions(
+ props.perspective,
+ ).onClick;
+ return {props.perspective} ;
};
function getGripperLabel(stretchTool: StretchTool) {
- switch (stretchTool) {
- case StretchTool.TABLET:
- return "Tablet";
- case StretchTool.GRIPPER:
- case StretchTool.DEX_GRIPPER:
- return "Gripper";
- default:
- return "Wrist";
- }
+ switch (stretchTool) {
+ case StretchTool.TABLET:
+ return "Tablet";
+ case StretchTool.GRIPPER:
+ case StretchTool.DEX_GRIPPER:
+ return "Gripper";
+ default:
+ return "Wrist";
+ }
}
diff --git a/src/pages/operator/tsx/layout_components/ComponentList.tsx b/src/pages/operator/tsx/layout_components/ComponentList.tsx
index d6fbceb3..7950ca91 100644
--- a/src/pages/operator/tsx/layout_components/ComponentList.tsx
+++ b/src/pages/operator/tsx/layout_components/ComponentList.tsx
@@ -1,22 +1,22 @@
import React from "react";
import {
- CustomizableComponent,
- CustomizableComponentProps,
- SharedState,
+ CustomizableComponent,
+ CustomizableComponentProps,
+ SharedState,
} from "./CustomizableComponent";
import { DropZone } from "./DropZone";
import {
- ParentComponentDefinition,
- ComponentDefinition,
- ComponentType,
+ ParentComponentDefinition,
+ ComponentDefinition,
+ ComponentType,
} from "../utils/component_definitions";
/** Properties for {@link ComponentList} */
export type ComponentListProps = {
- /** Path of the container element (e.g. the path to the tabs structure rendering) */
- path: string;
- sharedState: SharedState;
- definition: ParentComponentDefinition;
+ /** Path of the container element (e.g. the path to the tabs structure rendering) */
+ path: string;
+ sharedState: SharedState;
+ definition: ParentComponentDefinition;
};
/**
@@ -25,46 +25,46 @@ export type ComponentListProps = {
* @param props {@link ComponentListProps}
*/
export const ComponentList = (props: ComponentListProps) => {
- const { path } = props;
- const components = props.definition.children;
- return (
- <>
- {components.map((compDef: ComponentDefinition, index: number) => {
- const curPath = (path ? path + "-" : "") + `${index}`;
- const cProps: CustomizableComponentProps = {
- definition: compDef,
- path: curPath,
- sharedState: props.sharedState,
- };
- const { type } = compDef;
- return (
-
- {type !== ComponentType.RunStopButton &&
- type !== ComponentType.BatteryGuage ? (
-
+ const { path } = props;
+ const components = props.definition.children;
+ return (
+ <>
+ {components.map((compDef: ComponentDefinition, index: number) => {
+ const curPath = (path ? path + "-" : "") + `${index}`;
+ const cProps: CustomizableComponentProps = {
+ definition: compDef,
+ path: curPath,
+ sharedState: props.sharedState,
+ };
+ const { type } = compDef;
+ return (
+
+ {type !== ComponentType.RunStopButton &&
+ type !== ComponentType.BatteryGuage ? (
+
+ ) : (
+ <>>
+ )}
+
+
+ );
+ })}
+ {(components.length > 0 &&
+ components[components.length - 1].type !==
+ ComponentType.BatteryGuage) ||
+ components.length === 0 ? (
+
) : (
- <>>
+ <>>
)}
-
-
- );
- })}
- {(components.length > 0 &&
- components[components.length - 1].type !==
- ComponentType.BatteryGuage) ||
- components.length === 0 ? (
-
- ) : (
- <>>
- )}
- >
- );
+ >
+ );
};
diff --git a/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx b/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx
index f5c6b48d..0834fd32 100644
--- a/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx
+++ b/src/pages/operator/tsx/layout_components/CustomizableComponent.tsx
@@ -1,7 +1,7 @@
import React from "react";
import {
- ComponentDefinition,
- ComponentType,
+ ComponentDefinition,
+ ComponentType,
} from "../utils/component_definitions";
import { DropZoneState } from "./DropZone";
import { Panel } from "./Panel";
@@ -18,41 +18,41 @@ import { BatteryGuage } from "../static_components/BatteryGauge";
/** State required for all elements */
export type SharedState = {
- customizing: boolean;
- /** Called when user clicks on a component */
- onSelect: (def: ComponentDefinition, path?: string) => void;
- /** Remote robot video streams */
- remoteStreams: Map;
- /** State required for all dropzones */
- dropZoneState: DropZoneState;
- /** Path to the active component */
- selectedPath?: string;
- /** Mapping of each button pad function to a {@link ButtonState} */
- buttonStateMap?: ButtonStateMap;
- /** Whether or not to hide the button labels */
- hideLabels?: boolean;
- /** Whether or not the beta teleop cameras are being used */
- hasBetaTeleopKit: boolean;
- /** What tool is attached to the stretch gripper. */
- stretchTool: StretchTool;
+ customizing: boolean;
+ /** Called when user clicks on a component */
+ onSelect: (def: ComponentDefinition, path?: string) => void;
+ /** Remote robot video streams */
+ remoteStreams: Map;
+ /** State required for all dropzones */
+ dropZoneState: DropZoneState;
+ /** Path to the active component */
+ selectedPath?: string;
+ /** Mapping of each button pad function to a {@link ButtonState} */
+ buttonStateMap?: ButtonStateMap;
+ /** Whether or not to hide the button labels */
+ hideLabels?: boolean;
+ /** Whether or not the beta teleop cameras are being used */
+ hasBetaTeleopKit: boolean;
+ /** What tool is attached to the stretch gripper. */
+ stretchTool: StretchTool;
};
/** Properties for any of the customizable components: tabs, video streams, or
* button pads.
*/
export type CustomizableComponentProps = {
- /**
- * Path to the component
- * @example "0-2" would represent the 2nd child of the 0th element in the layout
- */
- path: string;
- /**
- * Definition of the component (all the info required to know that type
- * of component to render
- */
- definition: ComponentDefinition;
- /** see {@link SharedState} */
- sharedState: SharedState;
+ /**
+ * Path to the component
+ * @example "0-2" would represent the 2nd child of the 0th element in the layout
+ */
+ path: string;
+ /**
+ * Definition of the component (all the info required to know that type
+ * of component to render
+ */
+ definition: ComponentDefinition;
+ /** see {@link SharedState} */
+ sharedState: SharedState;
};
/**
@@ -62,35 +62,35 @@ export type CustomizableComponentProps = {
* @returns rendered component
*/
export const CustomizableComponent = (props: CustomizableComponentProps) => {
- if (!props.definition.type) {
- throw new Error(`Component at ${props.path} is missing type`);
- }
+ if (!props.definition.type) {
+ throw new Error(`Component at ${props.path} is missing type`);
+ }
- // switch on the component type to render specific type of component
- switch (props.definition.type) {
- case ComponentType.Panel:
- return ;
- case ComponentType.ButtonPad:
- return ;
- case ComponentType.CameraView:
- return ;
- case ComponentType.PredictiveDisplay:
- return ;
- case ComponentType.ButtonGrid:
- return ;
- case ComponentType.VirtualJoystick:
- return ;
- case ComponentType.Map:
- return ;
- case ComponentType.RunStopButton:
- return ;
- case ComponentType.BatteryGuage:
- 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.`,
- );
- }
+ // switch on the component type to render specific type of component
+ switch (props.definition.type) {
+ case ComponentType.Panel:
+ return ;
+ case ComponentType.ButtonPad:
+ return ;
+ case ComponentType.CameraView:
+ return ;
+ case ComponentType.PredictiveDisplay:
+ return ;
+ case ComponentType.ButtonGrid:
+ return ;
+ case ComponentType.VirtualJoystick:
+ return ;
+ case ComponentType.Map:
+ return ;
+ case ComponentType.RunStopButton:
+ return ;
+ case ComponentType.BatteryGuage:
+ 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.`,
+ );
+ }
};
/**
@@ -98,5 +98,5 @@ export const CustomizableComponent = (props: CustomizableComponentProps) => {
* @returns true if selected, otherwise false
*/
export function isSelected(props: CustomizableComponentProps): boolean {
- return props.path === props.sharedState.selectedPath;
+ return props.path === props.sharedState.selectedPath;
}
diff --git a/src/pages/operator/tsx/layout_components/DropZone.tsx b/src/pages/operator/tsx/layout_components/DropZone.tsx
index 64aeb32d..b0252eb8 100644
--- a/src/pages/operator/tsx/layout_components/DropZone.tsx
+++ b/src/pages/operator/tsx/layout_components/DropZone.tsx
@@ -1,11 +1,11 @@
import React from "react";
import {
- ComponentDefinition,
- ComponentType,
- TabDefinition,
- PanelDefinition,
- ParentComponentDefinition,
- LayoutDefinition,
+ ComponentDefinition,
+ ComponentType,
+ TabDefinition,
+ PanelDefinition,
+ ParentComponentDefinition,
+ LayoutDefinition,
} from "../utils/component_definitions";
import { SharedState } from "./CustomizableComponent";
import { className } from "shared/util";
@@ -14,25 +14,25 @@ import "operator/css/DropZone.css";
/** State required for drop zones */
export type DropZoneState = {
- /**
- * Callback when the DropZone is clicked to handle dropping the active
- * selected component into the position of the drop zone
- *
- * @param path path to the drop zone
- */
- onDrop: (path: string) => void;
- /** Definition of the active selected component */
- selectedDefinition?: ComponentDefinition;
+ /**
+ * Callback when the DropZone is clicked to handle dropping the active
+ * selected component into the position of the drop zone
+ *
+ * @param path path to the drop zone
+ */
+ onDrop: (path: string) => void;
+ /** Definition of the active selected component */
+ selectedDefinition?: ComponentDefinition;
};
/** Properties for {@link DropZone} */
export type DropZoneProps = {
- /** Path to the drop zone, same as CustomizableComponent path */
- path: string;
- /** Definition of the CustomizableComponent containing this drop zone */
- parentDef: ParentComponentDefinition;
- /** State shared between all components */
- sharedState: SharedState;
+ /** Path to the drop zone, same as CustomizableComponent path */
+ path: string;
+ /** Definition of the CustomizableComponent containing this drop zone */
+ parentDef: ParentComponentDefinition;
+ /** State shared between all components */
+ sharedState: SharedState;
};
/**
@@ -41,176 +41,177 @@ export type DropZoneProps = {
* @param props {@link DropZoneProps}
*/
export const DropZone = (props: DropZoneProps) => {
- const [showNewPanelModal, setShowNewPanelModal] =
- React.useState(false);
+ const [showNewPanelModal, setShowNewPanelModal] =
+ React.useState(false);
- function canDrop(): boolean {
- const { path, parentDef } = props;
- const { selectedPath } = props.sharedState;
- const { selectedDefinition } = props.sharedState.dropZoneState;
+ function canDrop(): boolean {
+ const { path, parentDef } = props;
+ const { selectedPath } = props.sharedState;
+ const { selectedDefinition } = props.sharedState.dropZoneState;
- // If no active object selected
- if (!selectedDefinition) {
- return false;
- }
+ // If no active object selected
+ if (!selectedDefinition) {
+ return false;
+ }
- // Must pass drop zone rules about which components can go where
- if (!dropzoneRules(selectedDefinition.type, parentDef.type)) return false;
+ // Must pass drop zone rules about which components can go where
+ if (!dropzoneRules(selectedDefinition.type, parentDef.type))
+ return false;
- // Don't need to check if dropzone is adjacent if the active component
- // is coming from the sidebar component provider
- if (!selectedPath) return true;
+ // Don't need to check if dropzone is adjacent if the active component
+ // is coming from the sidebar component provider
+ if (!selectedPath) return true;
- // If Layout Grid only has one child panel and the panel is selected, hide dropzones to the
- // left and right of the panel as these dropzones would not move the active panel
- if (
- parentDef.type == ComponentType.Layout &&
- selectedDefinition.type == ComponentType.Panel
- ) {
- let selectedLayoutGridIdx = Number(selectedPath?.split("-")[0]);
- let pathLayoutGridIdx = Number(path.split("-")[0]);
+ // If Layout Grid only has one child panel and the panel is selected, hide dropzones to the
+ // left and right of the panel as these dropzones would not move the active panel
+ if (
+ parentDef.type == ComponentType.Layout &&
+ selectedDefinition.type == ComponentType.Panel
+ ) {
+ let selectedLayoutGridIdx = Number(selectedPath?.split("-")[0]);
+ let pathLayoutGridIdx = Number(path.split("-")[0]);
- if (
- pathLayoutGridIdx === selectedLayoutGridIdx ||
- pathLayoutGridIdx === selectedLayoutGridIdx + 1
- ) {
- let parentIdx = Number(selectedPath?.split("-")[0]);
- let def = parentDef as LayoutDefinition;
- if (def.children[parentIdx]?.children.length < 2) return false;
- }
- }
+ if (
+ pathLayoutGridIdx === selectedLayoutGridIdx ||
+ pathLayoutGridIdx === selectedLayoutGridIdx + 1
+ ) {
+ let parentIdx = Number(selectedPath?.split("-")[0]);
+ let def = parentDef as LayoutDefinition;
+ if (def.children[parentIdx]?.children.length < 2) return false;
+ }
+ }
- // Can't drop if dropzone is right next to the active element
- // (that wouldn't move the active element at all)
- return !pathsAdjacent(selectedPath, path);
- }
+ // Can't drop if dropzone is right next to the active element
+ // (that wouldn't move the active element at all)
+ return !pathsAdjacent(selectedPath, path);
+ }
- /** Calls onDrop function from Operator with the path of this dropzone */
- function handleClick(e: React.MouseEvent) {
- if (!props.sharedState.customizing) return;
- e.stopPropagation();
- // If adding a new tabs component from the sidebar
- if (
- props.sharedState.dropZoneState.selectedDefinition?.type ===
- ComponentType.Panel &&
- props.sharedState.selectedPath === undefined
- ) {
- setShowNewPanelModal(true);
- return;
+ /** Calls onDrop function from Operator with the path of this dropzone */
+ function handleClick(e: React.MouseEvent) {
+ if (!props.sharedState.customizing) return;
+ e.stopPropagation();
+ // If adding a new tabs component from the sidebar
+ if (
+ props.sharedState.dropZoneState.selectedDefinition?.type ===
+ ComponentType.Panel &&
+ props.sharedState.selectedPath === undefined
+ ) {
+ setShowNewPanelModal(true);
+ return;
+ }
+ props.sharedState.dropZoneState.onDrop(props.path);
}
- props.sharedState.dropZoneState.onDrop(props.path);
- }
- /**
- * When adding a panel from the sidebar (so it's not already in the interface),
- * this adds a new tab child and drops the new panel into the drop zone.
- *
- * @param newTabName the name of the tab child within the new panel
- */
- function createNewPanel(newTabName: string) {
- if (
- props.sharedState.dropZoneState.selectedDefinition?.type !==
- ComponentType.Panel
- )
- throw Error(
- `Should only call createNewPanel() when the active selected component is of type Panel`,
- );
+ /**
+ * When adding a panel from the sidebar (so it's not already in the interface),
+ * this adds a new tab child and drops the new panel into the drop zone.
+ *
+ * @param newTabName the name of the tab child within the new panel
+ */
+ function createNewPanel(newTabName: string) {
+ if (
+ props.sharedState.dropZoneState.selectedDefinition?.type !==
+ ComponentType.Panel
+ )
+ throw Error(
+ `Should only call createNewPanel() when the active selected component is of type Panel`,
+ );
- const def = props.sharedState.dropZoneState
- .selectedDefinition as PanelDefinition;
+ const def = props.sharedState.dropZoneState
+ .selectedDefinition as PanelDefinition;
- if (def.children.length > 1)
- throw Error(
- `createNewPanel() called with active panel definition that already has children: ${def.children}`,
- );
+ if (def.children.length > 1)
+ throw Error(
+ `createNewPanel() called with active panel definition that already has children: ${def.children}`,
+ );
- if (props.sharedState.selectedPath !== undefined)
- throw Error(
- `Called createNewPanel() when active selected path was not undefined ${props.sharedState.selectedPath}`,
- );
+ if (props.sharedState.selectedPath !== undefined)
+ throw Error(
+ `Called createNewPanel() when active selected path was not undefined ${props.sharedState.selectedPath}`,
+ );
- // Create a child tab and add it to the Panel's children
- def.children.push({
- type: ComponentType.SingleTab,
- label: newTabName,
- children: [],
- } as TabDefinition);
+ // Create a child tab and add it to the Panel's children
+ def.children.push({
+ type: ComponentType.SingleTab,
+ label: newTabName,
+ children: [],
+ } as TabDefinition);
- // Drop the new panel into the drop zone
- props.sharedState.dropZoneState.onDrop(props.path);
- }
+ // Drop the new panel into the drop zone
+ props.sharedState.dropZoneState.onDrop(props.path);
+ }
- const isActive = props.sharedState.customizing && canDrop();
- const inTab = props.parentDef.type === ComponentType.Panel;
- const overlay = props.parentDef.type === ComponentType.CameraView;
- const standard = !(inTab || overlay);
+ const isActive = props.sharedState.customizing && canDrop();
+ const inTab = props.parentDef.type === ComponentType.Panel;
+ const overlay = props.parentDef.type === ComponentType.CameraView;
+ const standard = !(inTab || overlay);
- return (
-
-
- save_alt
-
-
-
- );
+ return (
+
+
+ save_alt
+
+
+
+ );
};
/** Popup to name the first tab when a new panel component is added to the interface. */
const NewPanelModal = (props: {
- /** If the modal should be shown. */
- show: boolean;
- /** Callback to change the state of `show` */
- setShow: (show: boolean) => void;
- /**
- * Callback to add the panel (drop into the dropzone).
- * @param tabName the name of the tab child in the new panel
- */
- addPanel: (tabName: string) => void;
+ /** If the modal should be shown. */
+ show: boolean;
+ /** Callback to change the state of `show` */
+ setShow: (show: boolean) => void;
+ /**
+ * Callback to add the panel (drop into the dropzone).
+ * @param tabName the name of the tab child in the new panel
+ */
+ addPanel: (tabName: string) => void;
}) => {
- const [text, setText] = React.useState("");
- /** Call `addPanel` with the text in the text entry. */
- function handleAccept() {
- if (text.length > 0) props.addPanel(text);
- }
- /** Update the text entry when the user types in it. */
- function handleChange(e: React.ChangeEvent) {
- setText(e.target.value);
- }
- return (
-
-
- New Tab Label
-
-
-
- );
+ const [text, setText] = React.useState("");
+ /** Call `addPanel` with the text in the text entry. */
+ function handleAccept() {
+ if (text.length > 0) props.addPanel(text);
+ }
+ /** Update the text entry when the user types in it. */
+ function handleChange(e: React.ChangeEvent) {
+ setText(e.target.value);
+ }
+ return (
+
+
+ New Tab Label
+
+
+
+ );
};
/**
@@ -221,34 +222,37 @@ const NewPanelModal = (props: {
* @returns true if the active component is allowed to be dropped, false otherwise
*/
function dropzoneRules(active: ComponentType, parent: ComponentType) {
- // Tabs can only go into layout
- if (
- active === ComponentType.Panel &&
- parent !== ComponentType.LayoutGrid &&
- parent !== ComponentType.Layout
- )
- return false;
+ // Tabs can only go into layout
+ if (
+ active === ComponentType.Panel &&
+ parent !== ComponentType.LayoutGrid &&
+ parent !== ComponentType.Layout
+ )
+ return false;
- // Single tab can only go into tabs
- if (active === ComponentType.SingleTab && parent !== ComponentType.Panel)
- return false;
+ // Single tab can only go into tabs
+ if (active === ComponentType.SingleTab && parent !== ComponentType.Panel)
+ return false;
- // Only tabs can go into panel
- if (
- active !== ComponentType.Panel &&
- (parent === ComponentType.LayoutGrid || parent == ComponentType.Layout)
- )
- return false;
+ // Only tabs can go into panel
+ if (
+ active !== ComponentType.Panel &&
+ (parent === ComponentType.LayoutGrid || parent == ComponentType.Layout)
+ )
+ return false;
- // Only single tab can go into tabs
- if (active !== ComponentType.SingleTab && parent === ComponentType.Panel)
- return false;
+ // Only single tab can go into tabs
+ if (active !== ComponentType.SingleTab && parent === ComponentType.Panel)
+ return false;
- // Only button pad can go into video stream
- if (active !== ComponentType.ButtonPad && parent === ComponentType.CameraView)
- return false;
+ // Only button pad can go into video stream
+ if (
+ active !== ComponentType.ButtonPad &&
+ parent === ComponentType.CameraView
+ )
+ return false;
- return true;
+ return true;
}
/**
@@ -262,24 +266,24 @@ function dropzoneRules(active: ComponentType, parent: ComponentType) {
* @returns true if the paths are directly adjacent, false otherwise
*/
function pathsAdjacent(selectedPath: string, path: string) {
- // Check paths same length
- const splitActivePath = selectedPath.split("-");
- const splitSelfPath = path.split("-");
- const sameLength = splitActivePath.length == splitSelfPath.length;
- if (!sameLength) return false;
+ // Check paths same length
+ const splitActivePath = selectedPath.split("-");
+ const splitSelfPath = path.split("-");
+ const sameLength = splitActivePath.length == splitSelfPath.length;
+ if (!sameLength) return false;
- // Should have same parent
- const activePrefix = splitActivePath.slice(0, -1);
- const selfPrefix = splitSelfPath.slice(0, -1);
- const matchingPrefix = activePrefix.every(
- (val, index) => val === selfPrefix[index],
- );
- if (!matchingPrefix) return false;
+ // Should have same parent
+ const activePrefix = splitActivePath.slice(0, -1);
+ const selfPrefix = splitSelfPath.slice(0, -1);
+ const matchingPrefix = activePrefix.every(
+ (val, index) => val === selfPrefix[index],
+ );
+ if (!matchingPrefix) return false;
- // Check if last indices are adjacent
- const activeLast = +splitActivePath.slice(-1)[0];
- const selfLast = +splitSelfPath.slice(-1)[0];
- const adjacent = activeLast == selfLast || activeLast + 1 == selfLast;
+ // Check if last indices are adjacent
+ const activeLast = +splitActivePath.slice(-1)[0];
+ const selfLast = +splitSelfPath.slice(-1)[0];
+ const adjacent = activeLast == selfLast || activeLast + 1 == selfLast;
- return adjacent;
+ return adjacent;
}
diff --git a/src/pages/operator/tsx/layout_components/Map.tsx b/src/pages/operator/tsx/layout_components/Map.tsx
index 9c10f407..c5dfe6be 100644
--- a/src/pages/operator/tsx/layout_components/Map.tsx
+++ b/src/pages/operator/tsx/layout_components/Map.tsx
@@ -1,8 +1,8 @@
import React, { useEffect } from "react";
import "latest-createjs";
import {
- CustomizableComponentProps,
- isSelected,
+ CustomizableComponentProps,
+ isSelected,
} from "./CustomizableComponent";
import { MapDefinition } from "../utils/component_definitions";
import { mapFunctionProvider, occupancyGrid } from "operator/tsx/index";
@@ -10,11 +10,11 @@ import "operator/css/Map.css";
import { Canvas } from "../static_components/Canvas";
import { OccupancyGrid } from "../static_components/OccupancyGrid";
import {
- AMCLPose,
- ROSOccupancyGrid,
- ROSPose,
- className,
- waitUntil,
+ AMCLPose,
+ ROSOccupancyGrid,
+ ROSPose,
+ className,
+ waitUntil,
} from "shared/util";
import ROSLIB from "roslib";
import { UnderMapButton } from "../function_providers/UnderMapFunctionProvider";
@@ -28,425 +28,440 @@ import { isMobile } from "react-device-detect";
import { RadioFunctions, RadioGroup } from "../basic_components/RadioGroup";
export enum MapFunction {
- GetMap,
- GetPose,
- MoveBase,
- GoalReached,
+ GetMap,
+ GetPose,
+ MoveBase,
+ GoalReached,
}
export interface MapFunctions {
- GetMap: ROSOccupancyGrid;
- GetPose: () => ROSLIB.Transform;
- MoveBase: (pose: ROSPose) => void;
- GoalReached: () => boolean;
- SelectGoal: () => boolean;
- SetSelectGoal: (selectGoal: boolean) => void;
+ GetMap: ROSOccupancyGrid;
+ GetPose: () => ROSLIB.Transform;
+ MoveBase: (pose: ROSPose) => void;
+ GoalReached: () => boolean;
+ SelectGoal: () => boolean;
+ SetSelectGoal: (selectGoal: boolean) => void;
}
export interface UnderMapFunctions {
- SelectGoal: (toggle: boolean) => void;
- CancelGoal: () => void;
- DeleteGoal: (goalId: number) => void;
- SaveGoal: (name: string) => void;
- LoadGoal: (goalID: number) => void;
- GetPose: () => ROSLIB.Transform;
- GetSavedPoseNames: () => string[];
- GetSavedPoseTypes: () => string[];
- GetSavedPoses: () => ROSLIB.Transform[];
- DisplayPoseMarkers: (
- toggle: boolean,
- poses: ROSLIB.Transform[],
- poseNames: string[],
- poseTypes: string[],
- ) => void;
- DisplayGoalMarker: (pose: ROSLIB.Vector3) => void;
- NavigateToAruco: (goalID: number) => void;
- Play: () => void;
- RemoveGoalMarker: () => void;
- GoalReached: () => Promise;
+ SelectGoal: (toggle: boolean) => void;
+ CancelGoal: () => void;
+ DeleteGoal: (goalId: number) => void;
+ SaveGoal: (name: string) => void;
+ LoadGoal: (goalID: number) => void;
+ GetPose: () => ROSLIB.Transform;
+ GetSavedPoseNames: () => string[];
+ GetSavedPoseTypes: () => string[];
+ GetSavedPoses: () => ROSLIB.Transform[];
+ DisplayPoseMarkers: (
+ toggle: boolean,
+ poses: ROSLIB.Transform[],
+ poseNames: string[],
+ poseTypes: string[],
+ ) => void;
+ DisplayGoalMarker: (pose: ROSLIB.Vector3) => void;
+ NavigateToAruco: (goalID: number) => void;
+ Play: () => void;
+ RemoveGoalMarker: () => void;
+ GoalReached: () => Promise;
}
export const Map = (props: CustomizableComponentProps) => {
- const definition = props.definition as MapDefinition;
- const [active, setActive] = React.useState(false);
- const [occupancyGrid, setOccupanyGrid] = React.useState();
- const [selectGoal, setSelectGoal] = React.useState(false);
- const { customizing, hideLabels } = props.sharedState;
- const selected = isSelected(props);
+ const definition = props.definition as MapDefinition;
+ const [active, setActive] = React.useState(false);
+ const [occupancyGrid, setOccupanyGrid] = React.useState();
+ const [selectGoal, setSelectGoal] = React.useState(false);
+ const { customizing, hideLabels } = props.sharedState;
+ const selected = isSelected(props);
- // Constrain the width or height when the stream gets too large
- React.useEffect(() => {
- let map = mapFn.GetMap;
- let width = map ? map.info.width : 60;
- let height = map ? map.info.height : 100;
- var canvas = new Canvas({
- divID: "map",
- className: "mapCanvas",
- width: width * 5, // Scale width to avoid blurriness when making map larger
- height: height * 5, // Scale height to avoid blurriness when making map larger
- });
- var occupancyGridMap = new OccupancyGrid({
- functs: mapFn,
- rootObject: canvas.scene!,
- });
- canvas.scaleToDimensions(occupancyGridMap.width, occupancyGridMap.height);
- setOccupanyGrid(occupancyGridMap);
- }, []);
+ // Constrain the width or height when the stream gets too large
+ React.useEffect(() => {
+ let map = mapFn.GetMap;
+ let width = map ? map.info.width : 60;
+ let height = map ? map.info.height : 100;
+ var canvas = new Canvas({
+ divID: "map",
+ className: "mapCanvas",
+ width: width * 5, // Scale width to avoid blurriness when making map larger
+ height: height * 5, // Scale height to avoid blurriness when making map larger
+ });
+ var occupancyGridMap = new OccupancyGrid({
+ functs: mapFn,
+ rootObject: canvas.scene!,
+ });
+ canvas.scaleToDimensions(
+ occupancyGridMap.width,
+ occupancyGridMap.height,
+ );
+ setOccupanyGrid(occupancyGridMap);
+ }, []);
- function handleSelect(event: React.MouseEvent) {
- event.stopPropagation();
- props.sharedState.onSelect(props.definition, props.path);
- }
+ function handleSelect(event: React.MouseEvent) {
+ event.stopPropagation();
+ props.sharedState.onSelect(props.definition, props.path);
+ }
- const handleSelectGoal = React.useCallback((selectGoal: boolean) => {
- setSelectGoal(selectGoal);
- mapFn.SelectGoal = (): boolean => {
- return selectGoal;
- };
- }, []);
+ const handleSelectGoal = React.useCallback((selectGoal: boolean) => {
+ setSelectGoal(selectGoal);
+ mapFn.SelectGoal = (): boolean => {
+ return selectGoal;
+ };
+ }, []);
- let mapFn: MapFunctions = {
- GetMap: mapFunctionProvider.provideFunctions(
- MapFunction.GetMap,
- ) as ROSOccupancyGrid,
- GetPose: mapFunctionProvider.provideFunctions(
- MapFunction.GetPose,
- ) as () => ROSLIB.Transform,
- MoveBase: mapFunctionProvider.provideFunctions(MapFunction.MoveBase) as (
- pose: ROSPose,
- ) => void,
- GoalReached: mapFunctionProvider.provideFunctions(
- MapFunction.GoalReached,
- ) as () => boolean,
- SelectGoal: (): boolean => {
- return selectGoal;
- },
- SetSelectGoal: (selectGoal: boolean) => {
- handleSelectGoal(selectGoal);
- },
- };
+ let mapFn: MapFunctions = {
+ GetMap: mapFunctionProvider.provideFunctions(
+ MapFunction.GetMap,
+ ) as ROSOccupancyGrid,
+ GetPose: mapFunctionProvider.provideFunctions(
+ MapFunction.GetPose,
+ ) as () => ROSLIB.Transform,
+ MoveBase: mapFunctionProvider.provideFunctions(
+ MapFunction.MoveBase,
+ ) as (pose: ROSPose) => void,
+ GoalReached: mapFunctionProvider.provideFunctions(
+ MapFunction.GoalReached,
+ ) as () => boolean,
+ SelectGoal: (): boolean => {
+ return selectGoal;
+ },
+ SetSelectGoal: (selectGoal: boolean) => {
+ handleSelectGoal(selectGoal);
+ },
+ };
- let underMapFn: UnderMapFunctions = {
- SelectGoal: underMapFunctionProvider.provideFunctions(
- UnderMapButton.SelectGoal,
- ) as () => void,
- CancelGoal: underMapFunctionProvider.provideFunctions(
- UnderMapButton.CancelGoal,
- ) as () => void,
- DeleteGoal: underMapFunctionProvider.provideFunctions(
- UnderMapButton.DeleteGoal,
- ) as (goalID: number) => void,
- SaveGoal: underMapFunctionProvider.provideFunctions(
- UnderMapButton.SaveGoal,
- ) as (name: string) => void,
- LoadGoal: underMapFunctionProvider.provideFunctions(
- UnderMapButton.LoadGoal,
- ) as () => void,
- GetPose: underMapFunctionProvider.provideFunctions(
- UnderMapButton.GetPose,
- ) as () => ROSLIB.Transform,
- GetSavedPoseNames: underMapFunctionProvider.provideFunctions(
- UnderMapButton.GetSavedPoseNames,
- ) as () => string[],
- GetSavedPoseTypes: underMapFunctionProvider.provideFunctions(
- UnderMapButton.GetSavedPoseTypes,
- ) as () => string[],
- GetSavedPoses: underMapFunctionProvider.provideFunctions(
- UnderMapButton.GetSavedPoses,
- ) as () => ROSLIB.Transform[],
- DisplayPoseMarkers: (
- toggle: boolean,
- poses: ROSLIB.Transform[],
- poseNames: string[],
- poseTypes: string[],
- ) => {
- return occupancyGrid!.displayPoseMarkers(
- toggle,
- poses,
- poseNames,
- poseTypes,
- );
- },
- DisplayGoalMarker: (pose: ROSLIB.Vector3) =>
- occupancyGrid!.createGoalMarker(pose.x, pose.y, true),
- NavigateToAruco: underMapFunctionProvider.provideFunctions(
- UnderMapButton.NavigateToAruco,
- ) as (goalID: number) => void,
- Play: () => occupancyGrid!.play(),
- RemoveGoalMarker: () => occupancyGrid!.removeGoalMarker(),
- GoalReached: underMapFunctionProvider.provideFunctions(
- UnderMapButton.GoalReached,
- ) as () => Promise,
- };
+ let underMapFn: UnderMapFunctions = {
+ SelectGoal: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.SelectGoal,
+ ) as () => void,
+ CancelGoal: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.CancelGoal,
+ ) as () => void,
+ DeleteGoal: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.DeleteGoal,
+ ) as (goalID: number) => void,
+ SaveGoal: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.SaveGoal,
+ ) as (name: string) => void,
+ LoadGoal: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.LoadGoal,
+ ) as () => void,
+ GetPose: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.GetPose,
+ ) as () => ROSLIB.Transform,
+ GetSavedPoseNames: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.GetSavedPoseNames,
+ ) as () => string[],
+ GetSavedPoseTypes: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.GetSavedPoseTypes,
+ ) as () => string[],
+ GetSavedPoses: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.GetSavedPoses,
+ ) as () => ROSLIB.Transform[],
+ DisplayPoseMarkers: (
+ toggle: boolean,
+ poses: ROSLIB.Transform[],
+ poseNames: string[],
+ poseTypes: string[],
+ ) => {
+ return occupancyGrid!.displayPoseMarkers(
+ toggle,
+ poses,
+ poseNames,
+ poseTypes,
+ );
+ },
+ DisplayGoalMarker: (pose: ROSLIB.Vector3) =>
+ occupancyGrid!.createGoalMarker(pose.x, pose.y, true),
+ NavigateToAruco: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.NavigateToAruco,
+ ) as (goalID: number) => void,
+ Play: () => occupancyGrid!.play(),
+ RemoveGoalMarker: () => occupancyGrid!.removeGoalMarker(),
+ GoalReached: underMapFunctionProvider.provideFunctions(
+ UnderMapButton.GoalReached,
+ ) as () => Promise,
+ };
- return (
-
-
- {!isMobile ?
Map : <>>}
-
- {
- !isMobile && (
- //
-
- )
- //
- }
-
- {isMobile && (
-
- )}
-
- );
+ return (
+
+
+ {!isMobile ?
Map : <>>}
+
+ {
+ !isMobile && (
+ //
+
+ )
+ //
+ }
+
+ {isMobile && (
+
+ )}
+
+ );
};
/**
* Buttons to display under the map.
*/
const UnderMapButtons = (props: {
- handleSelectGoal: (selectGoal: boolean) => void;
- functs: UnderMapFunctions;
- hideLabels?: boolean;
+ handleSelectGoal: (selectGoal: boolean) => void;
+ functs: UnderMapFunctions;
+ hideLabels?: boolean;
}) => {
- const [poses, setPoses] = useState(
- props.functs.GetSavedPoseNames(),
- );
- const [selectedIdx, setSelectedIdx] = React.useState();
- const [selectGoal, setSelectGoal] = React.useState(false);
- const [displayGoals, setDisplayGoals] = React.useState(false);
- const [showSavePoseModal, setShowSavePoseModal] = useState(false);
- const [play, setPlay] = useState(false);
+ const [poses, setPoses] = useState(
+ props.functs.GetSavedPoseNames(),
+ );
+ const [selectedIdx, setSelectedIdx] = React.useState();
+ const [selectGoal, setSelectGoal] = React.useState(false);
+ const [displayGoals, setDisplayGoals] = React.useState(false);
+ const [showSavePoseModal, setShowSavePoseModal] = useState(false);
+ const [play, setPlay] = useState(false);
- let radioFuncts: RadioFunctions = {
- Delete: (label: string) =>
- props.functs.DeleteGoal(props.functs.GetSavedPoseNames().indexOf(label)),
- GetLabels: () => props.functs.GetSavedPoseNames(),
- SelectedLabel: (label: string) =>
- setSelectedIdx(props.functs.GetSavedPoseNames().indexOf(label)),
- };
+ let radioFuncts: RadioFunctions = {
+ Delete: (label: string) =>
+ props.functs.DeleteGoal(
+ props.functs.GetSavedPoseNames().indexOf(label),
+ ),
+ GetLabels: () => props.functs.GetSavedPoseNames(),
+ SelectedLabel: (label: string) =>
+ setSelectedIdx(props.functs.GetSavedPoseNames().indexOf(label)),
+ };
- const SavePoseModal = (props: {
- functs: UnderMapFunctions;
- setShow: (show: boolean) => void;
- show: boolean;
- }) => {
- const [name, setName] = React.useState("");
- function handleAccept() {
- if (name.length > 0) {
- if (!poses.includes(name)) {
- setPoses((poses) => [...poses, name]);
+ const SavePoseModal = (props: {
+ functs: UnderMapFunctions;
+ setShow: (show: boolean) => void;
+ show: boolean;
+ }) => {
+ const [name, setName] = React.useState("");
+ function handleAccept() {
+ if (name.length > 0) {
+ if (!poses.includes(name)) {
+ setPoses((poses) => [...poses, name]);
+ }
+ props.functs.SaveGoal(name);
+ props.functs.DisplayPoseMarkers(
+ displayGoals,
+ props.functs.GetSavedPoses(),
+ props.functs.GetSavedPoseNames(),
+ props.functs.GetSavedPoseTypes(),
+ );
+ }
+ setName("");
}
- props.functs.SaveGoal(name);
- props.functs.DisplayPoseMarkers(
- displayGoals,
- props.functs.GetSavedPoses(),
- props.functs.GetSavedPoseNames(),
- props.functs.GetSavedPoseTypes(),
- );
- }
- setName("");
- }
- return (
-
- {/* Save Current Pose on Map
+ return (
+
+ {/* Save Current Pose on Map
*/}
-
- {/* Pose Name */}
- setName(e.target.value)}
- placeholder="Enter name of destination"
- />
-
-
- );
- };
+
+ {/* Pose Name */}
+ setName(e.target.value)}
+ placeholder="Enter name of destination"
+ />
+
+
+ );
+ };
- function formatNamesandTypes(
- names: string[],
- types: string[],
- ): React.JSX.Element[] {
- let elements: React.JSX.Element[] = [];
- names.map((name, index) => {
- elements.push(
-
- {types[index]} {name}
-
,
- );
- });
- return elements;
- }
+ function formatNamesandTypes(
+ names: string[],
+ types: string[],
+ ): React.JSX.Element[] {
+ let elements: React.JSX.Element[] = [];
+ names.map((name, index) => {
+ elements.push(
+
+ {types[index]} {name}
+
,
+ );
+ });
+ return elements;
+ }
- return !isMobile ? (
-
-
- {
- props.handleSelectGoal(!selectGoal);
- setSelectGoal(!selectGoal);
- if (selectGoal) props.functs.RemoveGoalMarker();
- else radioFuncts.SelectedLabel(undefined);
- }}
- label="Select Goal"
- />
- {!play && (
- {
- if (!play && selectGoal) {
- props.functs.Play();
- setPlay(true);
- setSelectGoal(false);
- props.functs
- .GoalReached()
- .then((goalReached) => setPlay(false));
- } else if (!play && selectedIdx != undefined) {
- let pose: ROSLIB.Vector3 = props.functs.LoadGoal(selectedIdx)!;
- props.functs.DisplayGoalMarker(pose);
- props.functs.NavigateToAruco(selectedIdx);
- setPlay(true);
- setSelectGoal(false);
- props.functs
- .GoalReached()
- .then((goalReached) => setPlay(false));
- }
- }}
- >
- Start
- play_circle
-
- )}
- {play && (
- {
- props.functs.CancelGoal();
- setPlay(!play);
- }}
- >
- Cancel
- cancel
-
- )}
- {
- setShowSavePoseModal(true);
- // Disable select goal to stop accidental navigation
- if (selectGoal) {
- props.handleSelectGoal(false);
- setSelectGoal(false);
- }
- }}
- >
- Save new destination
- save
-
-
-
-
-
- ) : (
-
-
-
-
{
- setShowSavePoseModal(true);
- // Disable select goal to stop accidental navigation
- if (selectGoal) {
- props.handleSelectGoal(false);
- setSelectGoal(false);
- }
- }}
- >
- Save new destination
- save
-
- {!play && (
-
{
- if (!play && selectGoal) {
- props.functs.Play();
- setPlay(true);
- setSelectGoal(false);
- props.functs
- .GoalReached()
- .then((goalReached) => setPlay(false));
- } else if (!play && selectedIdx != undefined) {
- let pose: ROSLIB.Vector3 = props.functs.LoadGoal(selectedIdx)!;
- props.functs.DisplayGoalMarker(pose);
- props.functs.NavigateToAruco(selectedIdx);
- setPlay(true);
- setSelectGoal(false);
- props.functs
- .GoalReached()
- .then((goalReached) => setPlay(false));
- }
- }}
- >
- Play
- play_circle
-
- )}
- {play && (
-
{
- props.functs.CancelGoal();
- setPlay(!play);
- }}
- >
- Cancel
- cancel
-
- )}
-
{
- props.handleSelectGoal(!selectGoal);
- setSelectGoal(!selectGoal);
- }}
- label="Select Goal"
- />
-
-
-
- );
+ return !isMobile ? (
+
+
+ {
+ props.handleSelectGoal(!selectGoal);
+ setSelectGoal(!selectGoal);
+ if (selectGoal) props.functs.RemoveGoalMarker();
+ else radioFuncts.SelectedLabel(undefined);
+ }}
+ label="Select Goal"
+ />
+ {!play && (
+ {
+ if (!play && selectGoal) {
+ props.functs.Play();
+ setPlay(true);
+ setSelectGoal(false);
+ props.functs
+ .GoalReached()
+ .then((goalReached) => setPlay(false));
+ } else if (!play && selectedIdx != undefined) {
+ let pose: ROSLIB.Vector3 =
+ props.functs.LoadGoal(selectedIdx)!;
+ props.functs.DisplayGoalMarker(pose);
+ props.functs.NavigateToAruco(selectedIdx);
+ setPlay(true);
+ setSelectGoal(false);
+ props.functs
+ .GoalReached()
+ .then((goalReached) => setPlay(false));
+ }
+ }}
+ >
+ Start
+ play_circle
+
+ )}
+ {play && (
+ {
+ props.functs.CancelGoal();
+ setPlay(!play);
+ }}
+ >
+ Cancel
+ cancel
+
+ )}
+ {
+ setShowSavePoseModal(true);
+ // Disable select goal to stop accidental navigation
+ if (selectGoal) {
+ props.handleSelectGoal(false);
+ setSelectGoal(false);
+ }
+ }}
+ >
+ Save new destination
+ save
+
+
+
+
+
+ ) : (
+
+
+
+
{
+ setShowSavePoseModal(true);
+ // Disable select goal to stop accidental navigation
+ if (selectGoal) {
+ props.handleSelectGoal(false);
+ setSelectGoal(false);
+ }
+ }}
+ >
+ Save new destination
+ save
+
+ {!play && (
+
{
+ if (!play && selectGoal) {
+ props.functs.Play();
+ setPlay(true);
+ setSelectGoal(false);
+ props.functs
+ .GoalReached()
+ .then((goalReached) => setPlay(false));
+ } else if (!play && selectedIdx != undefined) {
+ let pose: ROSLIB.Vector3 =
+ props.functs.LoadGoal(selectedIdx)!;
+ props.functs.DisplayGoalMarker(pose);
+ props.functs.NavigateToAruco(selectedIdx);
+ setPlay(true);
+ setSelectGoal(false);
+ props.functs
+ .GoalReached()
+ .then((goalReached) => setPlay(false));
+ }
+ }}
+ >
+ Play
+ play_circle
+
+ )}
+ {play && (
+
{
+ props.functs.CancelGoal();
+ setPlay(!play);
+ }}
+ >
+ Cancel
+ cancel
+
+ )}
+
{
+ props.handleSelectGoal(!selectGoal);
+ setSelectGoal(!selectGoal);
+ }}
+ label="Select Goal"
+ />
+
+
+
+ );
};
diff --git a/src/pages/operator/tsx/layout_components/MovementRecorder.tsx b/src/pages/operator/tsx/layout_components/MovementRecorder.tsx
index 5bb5c6bf..e64e42c4 100644
--- a/src/pages/operator/tsx/layout_components/MovementRecorder.tsx
+++ b/src/pages/operator/tsx/layout_components/MovementRecorder.tsx
@@ -10,212 +10,214 @@ import { RadioFunctions, RadioGroup } from "../basic_components/RadioGroup";
/** All the possible button functions */
export enum MovementRecorderFunction {
- Record,
- SaveRecording,
- StopRecording,
- SavedRecordingNames,
- DeleteRecording,
- LoadRecording,
- Cancel,
- DeleteRecordingName,
- LoadRecordingName,
+ Record,
+ SaveRecording,
+ StopRecording,
+ SavedRecordingNames,
+ DeleteRecording,
+ LoadRecording,
+ Cancel,
+ DeleteRecordingName,
+ LoadRecordingName,
}
export interface MovementRecorderFunctions {
- Record: () => void;
- SaveRecording: (name: string) => void;
- StopRecording: () => void;
- SavedRecordingNames: () => string[];
- DeleteRecording: (recordingID: number) => void;
- LoadRecording: (recordingID: number) => void;
+ Record: () => void;
+ SaveRecording: (name: string) => void;
+ StopRecording: () => void;
+ SavedRecordingNames: () => string[];
+ DeleteRecording: (recordingID: number) => void;
+ LoadRecording: (recordingID: number) => void;
}
export const MovementRecorder = (props: {
- hideLabels: boolean;
- globalRecord?: boolean;
- isRecording?: boolean;
+ hideLabels: boolean;
+ globalRecord?: boolean;
+ isRecording?: boolean;
}) => {
- let functions: MovementRecorderFunctions = {
- Record: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.Record,
- ) as () => void,
- SaveRecording: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.SaveRecording,
- ) as (name: string) => void,
- StopRecording: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.StopRecording,
- ) as () => void,
- SavedRecordingNames: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.SavedRecordingNames,
- ) as () => string[],
- DeleteRecording: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.DeleteRecording,
- ) as (recordingID: number) => void,
- LoadRecording: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.LoadRecording,
- ) as (recordingID: number) => void,
- };
+ let functions: MovementRecorderFunctions = {
+ Record: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.Record,
+ ) as () => void,
+ SaveRecording: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.SaveRecording,
+ ) as (name: string) => void,
+ StopRecording: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.StopRecording,
+ ) as () => void,
+ SavedRecordingNames: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.SavedRecordingNames,
+ ) as () => string[],
+ DeleteRecording: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.DeleteRecording,
+ ) as (recordingID: number) => void,
+ LoadRecording: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.LoadRecording,
+ ) as (recordingID: number) => void,
+ };
- let radioFuncts: RadioFunctions = {
- Delete: movementRecorderFunctionProvider.provideFunctions(
- MovementRecorderFunction.DeleteRecordingName,
- ) as (name: string) => void,
- GetLabels: functions.SavedRecordingNames,
- SelectedLabel: (label: string) =>
- setSelectedIdx(functions.SavedRecordingNames().indexOf(label)),
- };
+ let radioFuncts: RadioFunctions = {
+ Delete: movementRecorderFunctionProvider.provideFunctions(
+ MovementRecorderFunction.DeleteRecordingName,
+ ) as (name: string) => void,
+ GetLabels: functions.SavedRecordingNames,
+ SelectedLabel: (label: string) =>
+ setSelectedIdx(functions.SavedRecordingNames().indexOf(label)),
+ };
- const [recordings, setRecordings] = useState(
- functions.SavedRecordingNames(),
- );
- const [selectedIdx, setSelectedIdx] = React.useState();
- const [showSaveRecordingModal, setShowSaveRecordingModal] =
- useState(false);
- const [isRecording, setIsRecording] = React.useState(
- props.isRecording ? props.isRecording : false,
- );
+ const [recordings, setRecordings] = useState(
+ functions.SavedRecordingNames(),
+ );
+ const [selectedIdx, setSelectedIdx] = React.useState();
+ const [showSaveRecordingModal, setShowSaveRecordingModal] =
+ useState(false);
+ const [isRecording, setIsRecording] = React.useState(
+ props.isRecording ? props.isRecording : false,
+ );
- const SaveRecordingModal = (props: {
- setShow: (show: boolean) => void;
- show: boolean;
- }) => {
- const [name, setName] = React.useState("");
- function handleAccept() {
- if (name.length > 0) {
- if (!recordings.includes(name)) {
- setRecordings((recordings) => [...recordings, name]);
+ const SaveRecordingModal = (props: {
+ setShow: (show: boolean) => void;
+ show: boolean;
+ }) => {
+ const [name, setName] = React.useState("");
+ function handleAccept() {
+ if (name.length > 0) {
+ if (!recordings.includes(name)) {
+ setRecordings((recordings) => [...recordings, name]);
+ }
+ functions.SaveRecording(name);
+ }
+ setName("");
}
- functions.SaveRecording(name);
- }
- setName("");
- }
- return (
- functions.StopRecording()}
- id="save-recording-modal"
- acceptButtonText="Save"
- acceptDisabled={name.length < 1}
- size={isMobile ? "small" : "large"}
- mobile={isMobile}
- >
- {/* Save Recording
+ return (
+ functions.StopRecording()}
+ id="save-recording-modal"
+ acceptButtonText="Save"
+ acceptDisabled={name.length < 1}
+ size={isMobile ? "small" : "large"}
+ mobile={isMobile}
+ >
+ {/* Save Recording
*/}
-
- {/* Recording Name */}
- setName(e.target.value)}
- placeholder="Enter name of movement"
- />
-
-
- );
- };
+
+ {/* Recording Name */}
+ setName(e.target.value)}
+ placeholder="Enter name of movement"
+ />
+
+
+ );
+ };
- useEffect(() => {
- if (props.isRecording == undefined) {
- return;
- } else if (props.isRecording) {
- functions.Record();
- } else {
- setShowSaveRecordingModal(true);
- }
- }, [props.isRecording]);
+ useEffect(() => {
+ if (props.isRecording == undefined) {
+ return;
+ } else if (props.isRecording) {
+ functions.Record();
+ } else {
+ setShowSaveRecordingModal(true);
+ }
+ }, [props.isRecording]);
- if (props.globalRecord !== undefined && !props.globalRecord)
- return (
-
- );
+ if (props.globalRecord !== undefined && !props.globalRecord)
+ return (
+
+ );
- return !isMobile ? (
-
- Movement Recorder
-
-
-
- {
- if (selectedIdx != undefined) {
- functions.LoadRecording(selectedIdx);
- }
- }}
- >
- Play
- play_circle
-
-
-
- {
- if (!isRecording) {
- setIsRecording(true);
- functions.Record();
- } else {
- setIsRecording(false);
- setShowSaveRecordingModal(true);
- }
- }}
- >
- {!isRecording ? (
- Record
- ) : (
- Save
- )}
- {!isRecording ? (
- radio_button_checked
- ) : (
- save
- )}
-
-
-
- {
- if (selectedIdx != undefined) {
- functions.DeleteRecording(selectedIdx);
- }
- setRecordings(functions.SavedRecordingNames());
- setSelectedIdx(undefined);
- }}
- >
- Delete
- delete_forever
-
-
-
-
-
- ) : (
-
-
-
- {/*
{
+ return !isMobile ? (
+
+ Movement Recorder
+
+
+
+ {
+ if (selectedIdx != undefined) {
+ functions.LoadRecording(selectedIdx);
+ }
+ }}
+ >
+ Play
+ play_circle
+
+
+
+ {
+ if (!isRecording) {
+ setIsRecording(true);
+ functions.Record();
+ } else {
+ setIsRecording(false);
+ setShowSaveRecordingModal(true);
+ }
+ }}
+ >
+ {!isRecording ? (
+ Record
+ ) : (
+ Save
+ )}
+ {!isRecording ? (
+
+ radio_button_checked
+
+ ) : (
+ save
+ )}
+
+
+
+ {
+ if (selectedIdx != undefined) {
+ functions.DeleteRecording(selectedIdx);
+ }
+ setRecordings(functions.SavedRecordingNames());
+ setSelectedIdx(undefined);
+ }}
+ >
+ Delete
+ delete_forever
+
+
+
+
+
+ ) : (
+
+
+
+ {/*
{
if (!isRecording) {
setIsRecording(true)
functions.Record()
@@ -233,22 +235,22 @@ export const MovementRecorder = (props: {
}
{!isRecording ? Record : Save }
*/}
-
{
- if (selectedIdx != undefined && selectedIdx > -1) {
- functions.LoadRecording(selectedIdx);
- }
- }}
- >
- play_circle
- Play
-
-
-
-
- );
+
{
+ if (selectedIdx != undefined && selectedIdx > -1) {
+ functions.LoadRecording(selectedIdx);
+ }
+ }}
+ >
+ play_circle
+ Play
+
+
+
+
+ );
};
diff --git a/src/pages/operator/tsx/layout_components/Panel.tsx b/src/pages/operator/tsx/layout_components/Panel.tsx
index 98fb4e0c..e31c0e1b 100644
--- a/src/pages/operator/tsx/layout_components/Panel.tsx
+++ b/src/pages/operator/tsx/layout_components/Panel.tsx
@@ -1,17 +1,17 @@
import React from "react";
import {
- ComponentType,
- ParentComponentDefinition,
- TabDefinition,
- PanelDefinition,
+ ComponentType,
+ ParentComponentDefinition,
+ TabDefinition,
+ PanelDefinition,
} from "../utils/component_definitions";
import { className } from "shared/util";
import { PopupModal } from "../basic_components/PopupModal";
import { ComponentListProps, ComponentList } from "./ComponentList";
import { DropZone } from "./DropZone";
import {
- CustomizableComponentProps,
- isSelected,
+ CustomizableComponentProps,
+ isSelected,
} from "./CustomizableComponent";
import "operator/css/Panel.css";
@@ -27,222 +27,222 @@ implement behavior:
* @param props {@link CustomizableComponentProps}
*/
export const Panel = (props: CustomizableComponentProps) => {
- // Index of the active tab
- let [activeTab, setActiveTab] = React.useState(0);
- // If should show the popup to name a new tab
- const [showTabModal, setShowTabModal] = React.useState(false);
- const definition = props.definition as PanelDefinition;
- const countChildren = definition.children.length;
-
- // Handle case where active tab was moved or deleted, just use last remaining tab
- if (activeTab >= countChildren) {
- setActiveTab(countChildren - 1);
- activeTab = countChildren - 1;
- }
-
- const activeTabDef = definition.children[activeTab] as TabDefinition;
- if (!activeTabDef) {
- throw Error(
- `Tabs at: ${props.path}\nActive tab not defined\nActive tab: ${activeTab}`,
- );
- }
- if (activeTabDef.type != ComponentType.SingleTab) {
- throw Error(
- `Tabs element at path ${props.path} has child of type ${activeTabDef.type}`,
- );
- }
-
- // Should take up screen size proportional to number of children
- const flex =
- activeTabDef.label === "Safety"
- ? 1
- : Math.max(activeTabDef.children.length + 1, 1);
-
- /** Props for rendering the children elements inside the active tab */
- const componentListProps: ComponentListProps = {
- path: props.path + "-" + activeTab,
- sharedState: props.sharedState,
- // Use active tab as the definition for what to render
- definition: activeTabDef,
- };
-
- /**
- * Creates a definition for the new tab and adds it to the layout
- * @param name the label for the new tab
- */
- function addTab(name: string) {
- // Define new tab
- const newTabDef = {
- type: ComponentType.SingleTab,
- label: name,
- children: [],
- } as TabDefinition;
- // Add it as a new child
- definition.children.push(newTabDef);
- // Set as selected element (and rerender)
- props.sharedState.onSelect(newTabDef, undefined);
- }
-
- /**
- * Callback when a tab label is clicked on. Sets the tab as active. If already
- * active and in customize mode, selects the tab for customization.
- * @param idx index of the clicked tab
- */
- function clickTab(idx: number) {
- // If customizing and tab already active
- if (props.sharedState.customizing && idx === activeTab) {
- // Mark tab as selected
- const tabPath = props.path + "-" + idx;
- props.sharedState.onSelect(definition.children[idx], tabPath);
- return;
+ // Index of the active tab
+ let [activeTab, setActiveTab] = React.useState(0);
+ // If should show the popup to name a new tab
+ const [showTabModal, setShowTabModal] = React.useState(false);
+ const definition = props.definition as PanelDefinition;
+ const countChildren = definition.children.length;
+
+ // Handle case where active tab was moved or deleted, just use last remaining tab
+ if (activeTab >= countChildren) {
+ setActiveTab(countChildren - 1);
+ activeTab = countChildren - 1;
}
- // Set tab as active
- setActiveTab(idx);
- }
-
- /**
- * Handle click on tab content during customization mode. Marks this entire
- * tabs component as selected.
- */
- function selectContent() {
- props.sharedState.onSelect(props.definition, props.path);
- }
-
- // Add onClick listener to tab content in customization mode
- const selectProp = props.sharedState.customizing
- ? {
- onClick: selectContent,
- }
- : {};
-
- /**
- * Checks if this tabs or one of its immediate children is currently selected
- *
- * @returns null if currently selected component is not either this tabs
- * or one of it's immediate SingleTab children, -1 if the selected component
- * is this entire tabs component, or the index of the selected single tab
- * child.
- */
- function checkChildTabSelected(): number | null {
- const selectedPath = props.sharedState.selectedPath;
- if (!selectedPath) return null; // nothing is selected/active
- const activeSplitPath = selectedPath.split("-");
- const thisSplitPath = props.path.split("-");
- const activeChild = thisSplitPath.every(
- (val, index) => val === activeSplitPath[index],
- );
- if (!activeChild) return null; // active path is not a child element
- // The paths are exactly the same, the entire Tabs structure is selected
- if (activeSplitPath.length == thisSplitPath.length) return -1;
- // Path points to a child of a tab
- if (activeSplitPath.length - 1 > thisSplitPath.length) return null;
- // Return the child index
- return +activeSplitPath.slice(-1);
- }
- const childTabSelected: number | null = checkChildTabSelected();
-
- /**
- * Maps children list to a set of buttons with labels for switching tabs
- * @param tabDef definition of the child single tab component
- * @param idx index of the child component in the children array
- * @returns A button to switch tabs
- */
- function mapTabLabels(tabDef: TabDefinition, idx: number) {
- const active = activeTab === idx;
- const selected = childTabSelected === idx;
+
+ const activeTabDef = definition.children[activeTab] as TabDefinition;
+ if (!activeTabDef) {
+ throw Error(
+ `Tabs at: ${props.path}\nActive tab not defined\nActive tab: ${activeTab}`,
+ );
+ }
+ if (activeTabDef.type != ComponentType.SingleTab) {
+ throw Error(
+ `Tabs element at path ${props.path} has child of type ${activeTabDef.type}`,
+ );
+ }
+
+ // Should take up screen size proportional to number of children
+ const flex =
+ activeTabDef.label === "Safety"
+ ? 1
+ : Math.max(activeTabDef.children.length + 1, 1);
+
+ /** Props for rendering the children elements inside the active tab */
+ const componentListProps: ComponentListProps = {
+ path: props.path + "-" + activeTab,
+ sharedState: props.sharedState,
+ // Use active tab as the definition for what to render
+ definition: activeTabDef,
+ };
+
+ /**
+ * Creates a definition for the new tab and adds it to the layout
+ * @param name the label for the new tab
+ */
+ function addTab(name: string) {
+ // Define new tab
+ const newTabDef = {
+ type: ComponentType.SingleTab,
+ label: name,
+ children: [],
+ } as TabDefinition;
+ // Add it as a new child
+ definition.children.push(newTabDef);
+ // Set as selected element (and rerender)
+ props.sharedState.onSelect(newTabDef, undefined);
+ }
+
+ /**
+ * Callback when a tab label is clicked on. Sets the tab as active. If already
+ * active and in customize mode, selects the tab for customization.
+ * @param idx index of the clicked tab
+ */
+ function clickTab(idx: number) {
+ // If customizing and tab already active
+ if (props.sharedState.customizing && idx === activeTab) {
+ // Mark tab as selected
+ const tabPath = props.path + "-" + idx;
+ props.sharedState.onSelect(definition.children[idx], tabPath);
+ return;
+ }
+ // Set tab as active
+ setActiveTab(idx);
+ }
+
+ /**
+ * Handle click on tab content during customization mode. Marks this entire
+ * tabs component as selected.
+ */
+ function selectContent() {
+ props.sharedState.onSelect(props.definition, props.path);
+ }
+
+ // Add onClick listener to tab content in customization mode
+ const selectProp = props.sharedState.customizing
+ ? {
+ onClick: selectContent,
+ }
+ : {};
+
+ /**
+ * Checks if this tabs or one of its immediate children is currently selected
+ *
+ * @returns null if currently selected component is not either this tabs
+ * or one of it's immediate SingleTab children, -1 if the selected component
+ * is this entire tabs component, or the index of the selected single tab
+ * child.
+ */
+ function checkChildTabSelected(): number | null {
+ const selectedPath = props.sharedState.selectedPath;
+ if (!selectedPath) return null; // nothing is selected/active
+ const activeSplitPath = selectedPath.split("-");
+ const thisSplitPath = props.path.split("-");
+ const activeChild = thisSplitPath.every(
+ (val, index) => val === activeSplitPath[index],
+ );
+ if (!activeChild) return null; // active path is not a child element
+ // The paths are exactly the same, the entire Tabs structure is selected
+ if (activeSplitPath.length == thisSplitPath.length) return -1;
+ // Path points to a child of a tab
+ if (activeSplitPath.length - 1 > thisSplitPath.length) return null;
+ // Return the child index
+ return +activeSplitPath.slice(-1);
+ }
+ const childTabSelected: number | null = checkChildTabSelected();
+
+ /**
+ * Maps children list to a set of buttons with labels for switching tabs
+ * @param tabDef definition of the child single tab component
+ * @param idx index of the child component in the children array
+ * @returns A button to switch tabs
+ */
+ function mapTabLabels(tabDef: TabDefinition, idx: number) {
+ const active = activeTab === idx;
+ const selected = childTabSelected === idx;
+ return (
+
+
+ clickTab(idx)}
+ >
+ {tabDef.label}
+
+
+ );
+ }
+
+ const thisSelected = childTabSelected === -1;
+
return (
-
-
- clickTab(idx)}
+
- {tabDef.label}
-
-
+
+ {definition.children.map(mapTabLabels)}
+
+ {
+ // In customization mode show an extra plus to add a new tab
+ props.sharedState.customizing ? (
+ setShowTabModal(true)}
+ >
+ add_circle
+
+ ) : undefined
+ }
+
+
+
+
+
+
);
- }
-
- const thisSelected = childTabSelected === -1;
-
- return (
-
-
- {definition.children.map(mapTabLabels)}
-
- {
- // In customization mode show an extra plus to add a new tab
- props.sharedState.customizing ? (
- setShowTabModal(true)}
- >
- add_circle
-
- ) : undefined
- }
-
-
-
-
-
-
- );
};
/** Modal for creating a new tab on a panel component. */
const NewTabModal = (props: {
- show: boolean;
- setShow: (show: boolean) => void;
- addTab: (name: string) => void;
+ show: boolean;
+ setShow: (show: boolean) => void;
+ addTab: (name: string) => void;
}) => {
- const [text, setText] = React.useState("");
- function handleAccept() {
- if (text.length > 0) {
- props.addTab(text);
+ const [text, setText] = React.useState("");
+ function handleAccept() {
+ if (text.length > 0) {
+ props.addTab(text);
+ }
}
- }
-
- return (
-
-
- New Tab Label
-
- setText(e.target.value)}
- placeholder="label for the new tab"
- />
-
- );
+
+ return (
+
+
+ New Tab Label
+
+ setText(e.target.value)}
+ placeholder="label for the new tab"
+ />
+
+ );
};
diff --git a/src/pages/operator/tsx/layout_components/PredictiveDisplay.tsx b/src/pages/operator/tsx/layout_components/PredictiveDisplay.tsx
index a22e7a6b..52f2403b 100644
--- a/src/pages/operator/tsx/layout_components/PredictiveDisplay.tsx
+++ b/src/pages/operator/tsx/layout_components/PredictiveDisplay.tsx
@@ -3,9 +3,9 @@ import { className, navigationProps } from "shared/util";
import { CustomizableComponentProps } from "./CustomizableComponent";
import { predicitiveDisplayFunctionProvider } from "operator/tsx/index";
import {
- SVG_RESOLUTION,
- percent2Pixel,
- OVERHEAD_ROBOT_BASE as BASE,
+ SVG_RESOLUTION,
+ percent2Pixel,
+ OVERHEAD_ROBOT_BASE as BASE,
} from "../utils/svg";
import "operator/css/PredictiveDisplay.css";
@@ -15,7 +15,7 @@ import "operator/css/PredictiveDisplay.css";
* @returns scaled number
*/
function scaleToNavAspectRatio(y: number) {
- return (y / navigationProps.width) * navigationProps.height;
+ return (y / navigationProps.width) * navigationProps.height;
}
/**Arguments for drawing the dashed line in the center of the path */
@@ -37,14 +37,14 @@ const rotateArcRadius = percent2Pixel(10);
/** Functions required for predictive display */
export type PredictiveDisplayFunctions = {
- /** Callback function when mouse is clicked in predicitive display area */
- onClick: (length: number, angle: number) => void;
- /** Callback function when cursor is moved in predictive display area */
- onMove?: (length: number, angle: number) => void;
- /** Callback function for release */
- onRelease?: () => void;
- /** Callback function for leaving predictive display area */
- onLeave?: () => void;
+ /** Callback function when mouse is clicked in predicitive display area */
+ onClick: (length: number, angle: number) => void;
+ /** Callback function when cursor is moved in predictive display area */
+ onMove?: (length: number, angle: number) => void;
+ /** Callback function for release */
+ onRelease?: () => void;
+ /** Callback function for leaving predictive display area */
+ onLeave?: () => void;
};
/**
@@ -60,82 +60,82 @@ const customizingTrajectory = drawForwardTraj(106, 161)[2];
* @param props {@link CustomizableComponentProps}
*/
export const PredictiveDisplay = (props: CustomizableComponentProps) => {
- const svgRef = React.useRef(null);
- const { customizing } = props.sharedState;
- const [trajectory, setTrajectory] = React.useState(
- undefined,
- );
- const [moving, setMoving] = React.useState(false);
- const functions =
- predicitiveDisplayFunctionProvider.provideFunctions(setMoving);
- const length = React.useRef(0);
- const angle = React.useRef(0);
- const holding = React.useRef(false);
+ const svgRef = React.useRef(null);
+ const { customizing } = props.sharedState;
+ const [trajectory, setTrajectory] = React.useState(
+ undefined,
+ );
+ const [moving, setMoving] = React.useState(false);
+ const functions =
+ predicitiveDisplayFunctionProvider.provideFunctions(setMoving);
+ const length = React.useRef(0);
+ const angle = React.useRef(0);
+ const holding = React.useRef(false);
- function handleLeave() {
- setTrajectory(undefined);
- if (functions.onLeave) {
- functions.onLeave();
+ function handleLeave() {
+ setTrajectory(undefined);
+ if (functions.onLeave) {
+ functions.onLeave();
+ }
}
- }
- function handleClick() {
- holding.current = true;
- if (functions.onClick) {
- functions.onClick(length.current, angle.current);
+ function handleClick() {
+ holding.current = true;
+ if (functions.onClick) {
+ functions.onClick(length.current, angle.current);
+ }
}
- }
- function handleRelease() {
- holding.current = false;
- if (functions.onRelease) {
- functions.onRelease();
+ function handleRelease() {
+ holding.current = false;
+ if (functions.onRelease) {
+ functions.onRelease();
+ }
}
- }
- /** Rerenders the trajectory based on the cursor location */
- function handleMove(event: React.MouseEvent) {
- const { clientX, clientY } = event;
- const svg = svgRef.current;
- if (!svg) return;
+ /** Rerenders the trajectory based on the cursor location */
+ function handleMove(event: React.MouseEvent) {
+ const { clientX, clientY } = event;
+ const svg = svgRef.current;
+ if (!svg) return;
- // Get x and y in terms of the SVG element
- const rect = svg.getBoundingClientRect();
- const x = ((clientX - rect.left) / rect.width) * SVG_RESOLUTION;
- const pixelY = (clientY - rect.top) / rect.height;
- const y = scaleToNavAspectRatio(pixelY * SVG_RESOLUTION);
- const ret = drawTrajectory(x, y);
+ // Get x and y in terms of the SVG element
+ const rect = svg.getBoundingClientRect();
+ const x = ((clientX - rect.left) / rect.width) * SVG_RESOLUTION;
+ const pixelY = (clientY - rect.top) / rect.height;
+ const y = scaleToNavAspectRatio(pixelY * SVG_RESOLUTION);
+ const ret = drawTrajectory(x, y);
- length.current = ret[0];
- angle.current = ret[1];
- setTrajectory(ret[2]);
+ length.current = ret[0];
+ angle.current = ret[1];
+ setTrajectory(ret[2]);
- if (holding && functions.onMove) {
- functions.onMove(length.current, angle.current);
+ if (holding && functions.onMove) {
+ functions.onMove(length.current, angle.current);
+ }
}
- }
- // If customizing, disable all user interaction
- const controlProps = customizing
- ? {}
- : {
- onMouseMove: handleMove,
- onMouseLeave: handleLeave,
- onMouseDown: handleClick,
- onMouseUp: handleRelease,
- };
+ // If customizing, disable all user interaction
+ const controlProps = customizing
+ ? {}
+ : {
+ onMouseMove: handleMove,
+ onMouseLeave: handleLeave,
+ onMouseDown: handleClick,
+ onMouseUp: handleRelease,
+ };
- return (
-
- {customizing ? customizingTrajectory : trajectory}
-
- );
+ return (
+
+ {customizing ? customizingTrajectory : trajectory}
+
+ );
};
/**
@@ -146,17 +146,17 @@ export const PredictiveDisplay = (props: CustomizableComponentProps) => {
* @returns the linear distance, the angle, and the trajectory element
*/
function drawTrajectory(x: number, y: number): [number, number, JSX.Element] {
- let ret: [number, number, JSX.Element];
- if (y < baseFront) {
- ret = drawForwardTraj(x, y);
- } else if (y < baseBack) {
- // Next to base, draw rotate trajectory
- ret = drawRotate(x < BASE.centerX);
- } else {
- // Move backward
- ret = drawBackward(y);
- }
- return ret;
+ let ret: [number, number, JSX.Element];
+ if (y < baseFront) {
+ ret = drawForwardTraj(x, y);
+ } else if (y < baseBack) {
+ // Next to base, draw rotate trajectory
+ ret = drawRotate(x < BASE.centerX);
+ } else {
+ // Move backward
+ ret = drawBackward(y);
+ }
+ return ret;
}
/**
@@ -168,53 +168,60 @@ function drawTrajectory(x: number, y: number): [number, number, JSX.Element] {
* @returns the linear distance, the angle, and the trajectory element
*/
function drawForwardTraj(x: number, y: number): [number, number, JSX.Element] {
- const dx = BASE.centerX - x;
- const dy = baseFront - y;
- const heading = Math.atan2(-dx, dy);
- const sweepFlag = dx < 0;
+ const dx = BASE.centerX - x;
+ const dy = baseFront - y;
+ const heading = Math.atan2(-dx, dy);
+ const sweepFlag = dx < 0;
- const distance = Math.sqrt(dx * dx + dy * dy); // length from base to cursor
- const radius = distance / (2 * Math.sin(heading)); // radius of the center curve
- const centerPath = makeArc(BASE.centerX, baseFront, radius, sweepFlag, x, y);
+ const distance = Math.sqrt(dx * dx + dy * dy); // length from base to cursor
+ const radius = distance / (2 * Math.sin(heading)); // radius of the center curve
+ const centerPath = makeArc(
+ BASE.centerX,
+ baseFront,
+ radius,
+ sweepFlag,
+ x,
+ y,
+ );
- const leftEndX = x - (BASE.width / 2) * Math.cos(2 * heading);
- const leftEndY = y - (BASE.width / 2) * Math.sin(2 * heading);
- const leftRadius = radius + BASE.width / 2;
- const leftPath = makeArc(
- baseLeft,
- baseFront,
- leftRadius,
- sweepFlag,
- leftEndX,
- leftEndY,
- );
+ const leftEndX = x - (BASE.width / 2) * Math.cos(2 * heading);
+ const leftEndY = y - (BASE.width / 2) * Math.sin(2 * heading);
+ const leftRadius = radius + BASE.width / 2;
+ const leftPath = makeArc(
+ baseLeft,
+ baseFront,
+ leftRadius,
+ sweepFlag,
+ leftEndX,
+ leftEndY,
+ );
- const rightEndX = x + (BASE.width / 2) * Math.cos(2 * heading);
- const rightEndY = y + (BASE.width / 2) * Math.sin(2 * heading);
- const rightRadius = radius - BASE.width / 2;
- const rightPath = makeArc(
- baseRight,
- baseFront,
- rightRadius,
- sweepFlag,
- rightEndX,
- rightEndY,
- );
+ const rightEndX = x + (BASE.width / 2) * Math.cos(2 * heading);
+ const rightEndY = y + (BASE.width / 2) * Math.sin(2 * heading);
+ const rightRadius = radius - BASE.width / 2;
+ const rightPath = makeArc(
+ baseRight,
+ baseFront,
+ rightRadius,
+ sweepFlag,
+ rightEndX,
+ rightEndY,
+ );
- const trajectory = (
- <>
-
-
-
- >
- );
+ const trajectory = (
+ <>
+
+
+
+ >
+ );
- // Normalize the distance
- const maxX = SVG_RESOLUTION / 2;
- const maxY = baseFront;
- const maxDistance = Math.sqrt(maxX * maxX + maxY * maxY);
- const normalizedDistance = distance / maxDistance;
- return [normalizedDistance, -1 * heading, trajectory];
+ // Normalize the distance
+ const maxX = SVG_RESOLUTION / 2;
+ const maxY = baseFront;
+ const maxDistance = Math.sqrt(maxX * maxX + maxY * maxY);
+ const normalizedDistance = distance / maxDistance;
+ return [normalizedDistance, -1 * heading, trajectory];
}
/**
@@ -224,33 +231,33 @@ function drawForwardTraj(x: number, y: number): [number, number, JSX.Element] {
* @returns SVG path string description of the arrows
*/
function makeArrowPath(rotateLeft: boolean) {
- const arrowLength = percent2Pixel(2.5);
- const top = baseCenterY - rotateArcRadius;
- const bottom = baseCenterY + rotateArcRadius;
- const left = BASE.centerX - rotateArcRadius;
- const right = BASE.centerX + rotateArcRadius;
- const arrowDx = rotateLeft ? arrowLength : -arrowLength;
+ const arrowLength = percent2Pixel(2.5);
+ const top = baseCenterY - rotateArcRadius;
+ const bottom = baseCenterY + rotateArcRadius;
+ const left = BASE.centerX - rotateArcRadius;
+ const right = BASE.centerX + rotateArcRadius;
+ const arrowDx = rotateLeft ? arrowLength : -arrowLength;
- let arrows = makeArc(
- rotateLeft ? right : left,
- baseCenterY,
- rotateArcRadius,
- !rotateLeft,
- BASE.centerX,
- top,
- );
- arrows += `L ${BASE.centerX + arrowDx} ${top - arrowLength}`;
+ let arrows = makeArc(
+ rotateLeft ? right : left,
+ baseCenterY,
+ rotateArcRadius,
+ !rotateLeft,
+ BASE.centerX,
+ top,
+ );
+ arrows += `L ${BASE.centerX + arrowDx} ${top - arrowLength}`;
- arrows += makeArc(
- rotateLeft ? left : right,
- baseCenterY,
- rotateArcRadius,
- !rotateLeft,
- BASE.centerX,
- bottom,
- );
- arrows += `L ${BASE.centerX - arrowDx} ${bottom + arrowLength}`;
- return arrows;
+ arrows += makeArc(
+ rotateLeft ? left : right,
+ baseCenterY,
+ rotateArcRadius,
+ !rotateLeft,
+ BASE.centerX,
+ bottom,
+ );
+ arrows += `L ${BASE.centerX - arrowDx} ${bottom + arrowLength}`;
+ return arrows;
}
/** Path to draw for turning left in place */
@@ -266,9 +273,9 @@ const rightArrowPath: string = makeArrowPath(false);
* @returns the linear distance, the angle, and the trajectory element
*/
function drawRotate(rotateLeft: boolean): [number, number, JSX.Element] {
- const path = rotateLeft ? leftArrowPath : rightArrowPath;
- const trajectory = ;
- return [0, rotateLeft ? 1 : -1, trajectory];
+ const path = rotateLeft ? leftArrowPath : rightArrowPath;
+ const trajectory = ;
+ return [0, rotateLeft ? 1 : -1, trajectory];
}
/**
@@ -278,31 +285,31 @@ function drawRotate(rotateLeft: boolean): [number, number, JSX.Element] {
* @returns the linear distance, the angle, and the trajectory element
*/
function drawBackward(y: number): [number, number, JSX.Element] {
- const leftPath = `M ${baseLeft} ${baseBack} ${baseLeft} ${y}`;
- const rightPath = `M ${baseRight} ${baseBack} ${baseRight} ${y}`;
- const centerPath = `M ${BASE.centerX} ${baseBack} ${BASE.centerX} ${y}`;
- const trajectory = (
- <>
-
-
-
- >
- );
+ const leftPath = `M ${baseLeft} ${baseBack} ${baseLeft} ${y}`;
+ const rightPath = `M ${baseRight} ${baseBack} ${baseRight} ${y}`;
+ const centerPath = `M ${BASE.centerX} ${baseBack} ${BASE.centerX} ${y}`;
+ const trajectory = (
+ <>
+
+
+
+ >
+ );
- const distance = baseBack - y;
- const maxDistance = resolution_height - baseBack;
- return [distance / maxDistance, 0, trajectory];
+ const distance = baseBack - y;
+ const maxDistance = resolution_height - baseBack;
+ return [distance / maxDistance, 0, trajectory];
}
/**Formats the SVG path arc string. */
function makeArc(
- startX: number,
- startY: number,
- radius: number,
- sweepFlag: boolean,
- endX: number,
- endY: number,
+ startX: number,
+ startY: number,
+ radius: number,
+ sweepFlag: boolean,
+ endX: number,
+ endY: number,
) {
- const sweep = sweepFlag ? 1 : 0;
- return `M ${startX},${startY} A ${radius} ${radius} 0 0 ${sweep} ${endX},${endY}`;
+ const sweep = sweepFlag ? 1 : 0;
+ return `M ${startX},${startY} A ${radius} ${radius} 0 0 ${sweep} ${endX},${endY}`;
}
diff --git a/src/pages/operator/tsx/layout_components/SimpleCameraView.tsx b/src/pages/operator/tsx/layout_components/SimpleCameraView.tsx
index c9dcccaf..fbd55b68 100644
--- a/src/pages/operator/tsx/layout_components/SimpleCameraView.tsx
+++ b/src/pages/operator/tsx/layout_components/SimpleCameraView.tsx
@@ -3,9 +3,9 @@ import { className, RemoteStream } from "shared/util";
import { buttonFunctionProvider } from "..";
import { CameraViewId } from "../utils/component_definitions";
import {
- ButtonPadButton,
- ButtonState,
- panTiltButtons,
+ ButtonPadButton,
+ ButtonState,
+ panTiltButtons,
} from "../function_providers/ButtonFunctionProvider";
import "operator/css/SimpleCameraView.css";
import { getIcon } from "../utils/svg";
@@ -16,122 +16,132 @@ import { getIcon } from "../utils/svg";
* @param props properties
*/
export const SimpleCameraView = (props: {
- id: CameraViewId;
- remoteStreams: Map;
+ id: CameraViewId;
+ remoteStreams: Map;
}) => {
- // Reference to the video element
- const videoRef = React.useRef(null);
- // Get the stream to display inside the video
- const stream: MediaStream = getStream(props.id, props.remoteStreams);
- // Reference to the div immediately around the video element
- const videoAreaRef = React.useRef(null);
- // Boolean representing if the video stream needs to be constrained by height
- // (constrained by width otherwise)
- const [constrainedHeight, setConstrainedHeight] =
- React.useState(false);
+ // Reference to the video element
+ const videoRef = React.useRef(null);
+ // Get the stream to display inside the video
+ const stream: MediaStream = getStream(props.id, props.remoteStreams);
+ // Reference to the div immediately around the video element
+ const videoAreaRef = React.useRef(null);
+ // Boolean representing if the video stream needs to be constrained by height
+ // (constrained by width otherwise)
+ const [constrainedHeight, setConstrainedHeight] =
+ React.useState(false);
- // Update the source of the video stream
- React.useEffect(() => {
- if (!videoRef?.current) return;
- videoRef.current.srcObject = stream;
- }, [stream]);
+ // Update the source of the video stream
+ React.useEffect(() => {
+ if (!videoRef?.current) return;
+ videoRef.current.srcObject = stream;
+ }, [stream]);
- function setVideoSize(videoRef) {
- const videoRect = videoRef.current.getBoundingClientRect();
- let marginTop = 0;
- let min_control_panel_size = 300; // px
- marginTop = Math.min(
- window.innerHeight - min_control_panel_size - videoRect.height,
- 0,
- );
- document
- .querySelector(".btn-down")
- ?.setAttribute(
- "style",
- "margin-top:" + (videoRect.height + marginTop - 70).toString() + "px;",
- );
- document
- .querySelector(".depth-sensing")
- ?.setAttribute(
- "style",
- "margin-top:" + (videoRect.height + marginTop - 42).toString() + "px;",
- );
- videoRef.current.style.marginTop = marginTop.toString() + "px";
- }
+ function setVideoSize(videoRef) {
+ const videoRect = videoRef.current.getBoundingClientRect();
+ let marginTop = 0;
+ let min_control_panel_size = 300; // px
+ marginTop = Math.min(
+ window.innerHeight - min_control_panel_size - videoRect.height,
+ 0,
+ );
+ document
+ .querySelector(".btn-down")
+ ?.setAttribute(
+ "style",
+ "margin-top:" +
+ (videoRect.height + marginTop - 70).toString() +
+ "px;",
+ );
+ document
+ .querySelector(".depth-sensing")
+ ?.setAttribute(
+ "style",
+ "margin-top:" +
+ (videoRect.height + marginTop - 42).toString() +
+ "px;",
+ );
+ videoRef.current.style.marginTop = marginTop.toString() + "px";
+ }
- // Constrain the width or height when the stream gets too large
- React.useEffect(() => {
- const resizeObserver = new ResizeObserver((entries) => {
- // height and width of area around the video stream
- const { height, width } = entries[0].contentRect;
+ // Constrain the width or height when the stream gets too large
+ React.useEffect(() => {
+ const resizeObserver = new ResizeObserver((entries) => {
+ // height and width of area around the video stream
+ const { height, width } = entries[0].contentRect;
- // height and width of video stream
- if (!videoRef?.current) return;
- const videoRect = videoRef.current.getBoundingClientRect();
+ // height and width of video stream
+ if (!videoRef?.current) return;
+ const videoRect = videoRef.current.getBoundingClientRect();
- if (videoRect.height > height) {
- setConstrainedHeight(true);
- } else if (videoRect.width > width) {
- setConstrainedHeight(false);
- }
- });
- if (!videoAreaRef?.current) return;
- resizeObserver.observe(videoAreaRef.current);
- return () => resizeObserver.disconnect();
- }, []);
+ if (videoRect.height > height) {
+ setConstrainedHeight(true);
+ } else if (videoRect.width > width) {
+ setConstrainedHeight(false);
+ }
+ });
+ if (!videoAreaRef?.current) return;
+ resizeObserver.observe(videoAreaRef.current);
+ return () => resizeObserver.disconnect();
+ }, []);
- const videoComponent =
- props.id === CameraViewId.realsense ||
- props.id === CameraViewId.overhead ? (
- <>
-
- {panTiltButtons.map((dir) => (
-
- ))}
-
- setVideoSize(videoRef)}
- />
-
-
- >
- ) : (
- <>
-
-
setVideoSize(videoRef)}
- />
+ const videoComponent =
+ props.id === CameraViewId.realsense ||
+ props.id === CameraViewId.overhead ? (
+ <>
+
+ {panTiltButtons.map((dir) => (
+
+ ))}
+
+ setVideoSize(videoRef)}
+ />
+
+
+ >
+ ) : (
+ <>
+
+ setVideoSize(videoRef)}
+ />
+
+ >
+ );
+
+ return (
+
+ {videoComponent}
- >
);
-
- return (
-
- {videoComponent}
-
- );
};
/**
@@ -140,44 +150,44 @@ export const SimpleCameraView = (props: {
* @param props the direction of the button {@link PanTiltButton}
*/
const PanTiltButton = (props: { direction: ButtonPadButton }) => {
- const functs = buttonFunctionProvider.provideFunctions(props.direction);
- const dir = props.direction.split(" ")[2];
- let rotation: string;
+ const functs = buttonFunctionProvider.provideFunctions(props.direction);
+ const dir = props.direction.split(" ")[2];
+ let rotation: string;
- // Specify button details based on the direction
- switch (props.direction) {
- case ButtonPadButton.CameraTiltUp:
- rotation = "-90";
- break;
- case ButtonPadButton.CameraTiltDown:
- rotation = "90";
- break;
- case ButtonPadButton.CameraPanLeft:
- rotation = "180";
- break;
- case ButtonPadButton.CameraPanRight:
- rotation = "0"; // by default the arrow icon points right
- break;
- default:
- throw Error(`unknown pan tilt button direction ${props.direction}`);
- }
+ // Specify button details based on the direction
+ switch (props.direction) {
+ case ButtonPadButton.CameraTiltUp:
+ rotation = "-90";
+ break;
+ case ButtonPadButton.CameraTiltDown:
+ rotation = "90";
+ break;
+ case ButtonPadButton.CameraPanLeft:
+ rotation = "180";
+ break;
+ case ButtonPadButton.CameraPanRight:
+ rotation = "0"; // by default the arrow icon points right
+ break;
+ default:
+ throw Error(`unknown pan tilt button direction ${props.direction}`);
+ }
- return (
-
- {/* */}
-
- arrow_right
-
-
- );
+ return (
+
+ {/* */}
+
+ arrow_right
+
+
+ );
};
/*******************************************************************************
@@ -192,22 +202,22 @@ const PanTiltButton = (props: { direction: ButtonPadButton }) => {
* @returns the corresponding stream
*/
function getStream(
- id: CameraViewId,
- remoteStreams: Map,
+ id: CameraViewId,
+ remoteStreams: Map,
): MediaStream {
- let streamName: string;
- switch (id) {
- case CameraViewId.overhead:
- streamName = "overhead";
- break;
- case CameraViewId.realsense:
- streamName = "realsense";
- break;
- case CameraViewId.gripper:
- streamName = "gripper";
- break;
- default:
- throw Error(`unknow video stream id: ${id}`);
- }
- return remoteStreams.get(streamName)!.stream;
+ let streamName: string;
+ switch (id) {
+ case CameraViewId.overhead:
+ streamName = "overhead";
+ break;
+ case CameraViewId.realsense:
+ streamName = "realsense";
+ break;
+ case CameraViewId.gripper:
+ streamName = "gripper";
+ break;
+ default:
+ throw Error(`unknow video stream id: ${id}`);
+ }
+ return remoteStreams.get(streamName)!.stream;
}
diff --git a/src/pages/operator/tsx/layout_components/TextToSpeech.tsx b/src/pages/operator/tsx/layout_components/TextToSpeech.tsx
index 5309cfc9..34f69323 100644
--- a/src/pages/operator/tsx/layout_components/TextToSpeech.tsx
+++ b/src/pages/operator/tsx/layout_components/TextToSpeech.tsx
@@ -8,124 +8,126 @@ import { isMobile } from "react-device-detect";
/** All the possible button functions */
export enum TextToSpeechFunction {
- Play,
- Stop,
- SaveText,
- DeleteText,
- SavedTexts,
+ Play,
+ Stop,
+ SaveText,
+ DeleteText,
+ SavedTexts,
}
export interface TextToSpeechFunctions {
- Play: (text: string) => void;
- Stop: () => void;
- SaveText: (text: string) => void;
- DeleteText: (text: string) => void;
- SavedTexts: () => string[];
+ Play: (text: string) => void;
+ Stop: () => void;
+ SaveText: (text: string) => void;
+ DeleteText: (text: string) => void;
+ SavedTexts: () => string[];
}
export const TextToSpeech = (props: { hideLabels: boolean }) => {
- let functions: TextToSpeechFunctions = {
- SaveText: textToSpeechFunctionProvider.provideFunctions(
- TextToSpeechFunction.SaveText,
- ) as (name: string) => void,
- DeleteText: textToSpeechFunctionProvider.provideFunctions(
- TextToSpeechFunction.DeleteText,
- ) as (text: string) => void,
- SavedTexts: textToSpeechFunctionProvider.provideFunctions(
- TextToSpeechFunction.SavedTexts,
- ) as () => string[],
- Play: textToSpeechFunctionProvider.provideFunctions(
- TextToSpeechFunction.Play,
- ) as (text: string) => void,
- Stop: textToSpeechFunctionProvider.provideFunctions(
- TextToSpeechFunction.Stop,
- ) as () => void,
- };
+ let functions: TextToSpeechFunctions = {
+ SaveText: textToSpeechFunctionProvider.provideFunctions(
+ TextToSpeechFunction.SaveText,
+ ) as (name: string) => void,
+ DeleteText: textToSpeechFunctionProvider.provideFunctions(
+ TextToSpeechFunction.DeleteText,
+ ) as (text: string) => void,
+ SavedTexts: textToSpeechFunctionProvider.provideFunctions(
+ TextToSpeechFunction.SavedTexts,
+ ) as () => string[],
+ Play: textToSpeechFunctionProvider.provideFunctions(
+ TextToSpeechFunction.Play,
+ ) as (text: string) => void,
+ Stop: textToSpeechFunctionProvider.provideFunctions(
+ TextToSpeechFunction.Stop,
+ ) as () => void,
+ };
- const [savedTexts, setSavedTexts] = useState(
- functions.SavedTexts(),
- );
- const [selectedIdx, setSelectedIdx] = React.useState(
- undefined,
- );
- const [text, setText] = React.useState("");
+ const [savedTexts, setSavedTexts] = useState(
+ functions.SavedTexts(),
+ );
+ const [selectedIdx, setSelectedIdx] = React.useState(
+ undefined,
+ );
+ const [text, setText] = React.useState("");
- return !isMobile ? (
-
- Text-to-Speech
-
-
- {/* Play the text */}
-
- {
- functions.Play(text);
- }}
- >
- Play
- play_circle
-
-
- {/* Stop the playing text */}
-
- {
- functions.Stop();
- }}
- >
- Stop
- stop_circle
-
-
- {/* If we are on saved text, then we show a delete button, else a save button. */}
- {selectedIdx != undefined ? (
-
- {
- if (selectedIdx != undefined) {
- functions.DeleteText(text.trim());
- }
- setSavedTexts(functions.SavedTexts());
- setSelectedIdx(undefined);
- }}
- >
- Delete
- delete_forever
-
-
- ) : (
-
- {
- functions.SaveText(text.trim());
- setSavedTexts(functions.SavedTexts());
- }}
- >
- Save
- save
-
-
- )}
-
-
- ) : (
-
-
- Text-to-speech not yet implemented for mobile
-
-
- );
+ return !isMobile ? (
+
+ Text-to-Speech
+
+
+ {/* Play the text */}
+
+ {
+ functions.Play(text);
+ }}
+ >
+ Play
+ play_circle
+
+
+ {/* Stop the playing text */}
+
+ {
+ functions.Stop();
+ }}
+ >
+ Stop
+ stop_circle
+
+
+ {/* If we are on saved text, then we show a delete button, else a save button. */}
+ {selectedIdx != undefined ? (
+
+ {
+ if (selectedIdx != undefined) {
+ functions.DeleteText(text.trim());
+ }
+ setSavedTexts(functions.SavedTexts());
+ setSelectedIdx(undefined);
+ }}
+ >
+ Delete
+
+ delete_forever
+
+
+
+ ) : (
+
+ {
+ functions.SaveText(text.trim());
+ setSavedTexts(functions.SavedTexts());
+ }}
+ >
+ Save
+ save
+
+
+ )}
+
+
+ ) : (
+
+
+ Text-to-speech not yet implemented for mobile
+
+
+ );
};
diff --git a/src/pages/operator/tsx/layout_components/VirtualJoystick.tsx b/src/pages/operator/tsx/layout_components/VirtualJoystick.tsx
index 1d85ebc4..8553a5d6 100644
--- a/src/pages/operator/tsx/layout_components/VirtualJoystick.tsx
+++ b/src/pages/operator/tsx/layout_components/VirtualJoystick.tsx
@@ -1,7 +1,7 @@
import React from "react";
import {
- CustomizableComponentProps,
- isSelected,
+ CustomizableComponentProps,
+ isSelected,
} from "./CustomizableComponent";
import { className } from "shared/util";
import { SVG_RESOLUTION } from "../utils/svg";
@@ -12,121 +12,121 @@ const OUTER_RADIUS = SVG_RESOLUTION / 2;
const JOYSTICK_RADIUS = SVG_RESOLUTION / 3.3;
export const VirtualJoystick = (props: CustomizableComponentProps) => {
- const svgRef = React.useRef(null);
- const [active, setActive] = React.useState(false);
- const { customizing } = props.sharedState;
- const selected = isSelected(props);
- const [joystick, setJoystick] =
- React.useState(drawJoystickCenter());
- const functions =
- predicitiveDisplayFunctionProvider.provideFunctions(handleSetActive);
-
- function handleSetActive(active: boolean) {
- if (!active) {
- setJoystick(drawJoystickCenter());
+ const svgRef = React.useRef(null);
+ const [active, setActive] = React.useState(false);
+ const { customizing } = props.sharedState;
+ const selected = isSelected(props);
+ const [joystick, setJoystick] =
+ React.useState(drawJoystickCenter());
+ const functions =
+ predicitiveDisplayFunctionProvider.provideFunctions(handleSetActive);
+
+ function handleSetActive(active: boolean) {
+ if (!active) {
+ setJoystick(drawJoystickCenter());
+ }
+ setActive(active);
}
- setActive(active);
- }
- const length = React.useRef(0);
- const angle = React.useRef(0);
+ const length = React.useRef(0);
+ const angle = React.useRef(0);
- function drawJoystickCenter() {
- return drawJoystick(SVG_RESOLUTION / 2, SVG_RESOLUTION / 2);
- }
+ function drawJoystickCenter() {
+ return drawJoystick(SVG_RESOLUTION / 2, SVG_RESOLUTION / 2);
+ }
- function drawJoystick(x: number, y: number) {
- const newJoystick = (
-
- );
- return newJoystick;
- }
+ function drawJoystick(x: number, y: number) {
+ const newJoystick = (
+
+ );
+ return newJoystick;
+ }
+
+ function handleLeave() {
+ setJoystick(drawJoystickCenter);
+ if (functions.onLeave) {
+ functions.onLeave();
+ }
+ }
+
+ function handleClick(event: React.MouseEvent) {
+ if (!active) {
+ setJoystickToMouse(event);
+ }
+ if (functions.onClick) functions.onClick(length.current, angle.current);
+ }
+
+ function handleRelease() {
+ if (functions.onRelease) functions.onRelease();
+ }
+
+ function setLengthAndWidth(x: number, y: number) {
+ const xLocal = x - SVG_RESOLUTION / 2;
+ const yLocal = y - SVG_RESOLUTION / 2;
+
+ angle.current = -xLocal / (SVG_RESOLUTION / 2);
+ length.current = -yLocal / (SVG_RESOLUTION / 2);
+ }
- function handleLeave() {
- setJoystick(drawJoystickCenter);
- if (functions.onLeave) {
- functions.onLeave();
+ function setJoystickToMouse(
+ event: React.MouseEvent,
+ ): [number, number] {
+ const { clientX, clientY } = event;
+ const svg = svgRef.current;
+ if (!svg) return [0, 0];
+
+ // Get x and y in terms of the SVG element
+ const rect = svg.getBoundingClientRect();
+ const x = ((clientX - rect.left) / rect.width) * SVG_RESOLUTION;
+ const y = ((clientY - rect.top) / rect.height) * SVG_RESOLUTION;
+ setLengthAndWidth(x, y);
+ setJoystick(drawJoystick(x, y));
+ return [x, y];
}
- }
- function handleClick(event: React.MouseEvent) {
- if (!active) {
- setJoystickToMouse(event);
+ function handleMove(event: React.MouseEvent) {
+ if (!active) return;
+ setJoystickToMouse(event);
+ if (functions.onMove) functions.onMove(length.current, angle.current);
}
- if (functions.onClick) functions.onClick(length.current, angle.current);
- }
-
- function handleRelease() {
- if (functions.onRelease) functions.onRelease();
- }
-
- function setLengthAndWidth(x: number, y: number) {
- const xLocal = x - SVG_RESOLUTION / 2;
- const yLocal = y - SVG_RESOLUTION / 2;
-
- angle.current = -xLocal / (SVG_RESOLUTION / 2);
- length.current = -yLocal / (SVG_RESOLUTION / 2);
- }
-
- function setJoystickToMouse(
- event: React.MouseEvent,
- ): [number, number] {
- const { clientX, clientY } = event;
- const svg = svgRef.current;
- if (!svg) return [0, 0];
-
- // Get x and y in terms of the SVG element
- const rect = svg.getBoundingClientRect();
- const x = ((clientX - rect.left) / rect.width) * SVG_RESOLUTION;
- const y = ((clientY - rect.top) / rect.height) * SVG_RESOLUTION;
- setLengthAndWidth(x, y);
- setJoystick(drawJoystick(x, y));
- return [x, y];
- }
-
- function handleMove(event: React.MouseEvent) {
- if (!active) return;
- setJoystickToMouse(event);
- if (functions.onMove) functions.onMove(length.current, angle.current);
- }
-
- function handleSelect(event: React.MouseEvent) {
- event.stopPropagation();
- props.sharedState.onSelect(props.definition, props.path);
- }
-
- const controlProps = customizing
- ? { onClick: handleSelect }
- : {
- onPointerMove: active ? handleMove : undefined,
- onPointerLeave: handleLeave,
- onPointerDown: handleClick,
- onPointerUp: handleRelease,
- };
- return (
-
-
-
- {/*
+
+ function handleSelect(event: React.MouseEvent) {
+ event.stopPropagation();
+ props.sharedState.onSelect(props.definition, props.path);
+ }
+
+ const controlProps = customizing
+ ? { onClick: handleSelect }
+ : {
+ onPointerMove: active ? handleMove : undefined,
+ onPointerLeave: handleLeave,
+ onPointerDown: handleClick,
+ onPointerUp: handleRelease,
+ };
+ return (
+
+
+
+ {/*
*/}
- {joystick}
-
-
- );
+ {joystick}
+
+
+ );
};
diff --git a/src/pages/operator/tsx/static_components/AudioControl.tsx b/src/pages/operator/tsx/static_components/AudioControl.tsx
index dfc40e0a..8ea26361 100644
--- a/src/pages/operator/tsx/static_components/AudioControl.tsx
+++ b/src/pages/operator/tsx/static_components/AudioControl.tsx
@@ -4,57 +4,57 @@ import React from "react";
/**Props for {@link AudioControl} */
type AudioControlProps = {
- /** Remote robot video streams */
- remoteStreams: Map;
+ /** Remote robot video streams */
+ remoteStreams: Map;
};
export const AudioControl = (props: AudioControlProps) => {
- // Create the audio element for listening to audio from the robot
- const audioRef = React.useRef(null);
+ // Create the audio element for listening to audio from the robot
+ const audioRef = React.useRef(null);
- // Keep track of whether the audio stream is muted or not.
- // NOTE: Audio must start muted for AutoPlay to work (see below)
- // https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability
- const [muted, setMuted] = React.useState(true);
+ // Keep track of whether the audio stream is muted or not.
+ // NOTE: Audio must start muted for AutoPlay to work (see below)
+ // https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability
+ const [muted, setMuted] = React.useState(true);
- // Assign the source of the audio element to the audio stream from the robot
- const stream = React.useMemo(() => {
- if (props.remoteStreams.has("audio")) {
- return props.remoteStreams.get("audio")?.stream;
- } else {
- return null;
- }
- }, [props.remoteStreams]);
- React.useEffect(() => {
- if (!audioRef?.current) return;
- if (stream) {
- audioRef.current.srcObject = stream;
- }
- }, [audioRef.current, stream]);
+ // Assign the source of the audio element to the audio stream from the robot
+ const stream = React.useMemo(() => {
+ if (props.remoteStreams.has("audio")) {
+ return props.remoteStreams.get("audio")?.stream;
+ } else {
+ return null;
+ }
+ }, [props.remoteStreams]);
+ React.useEffect(() => {
+ if (!audioRef?.current) return;
+ if (stream) {
+ audioRef.current.srcObject = stream;
+ }
+ }, [audioRef.current, stream]);
- // Callback for when the user presses the button to toggle mute
- const toggleMute = React.useCallback(() => {
- setMuted((prev) => !prev);
- }, [setMuted]);
+ // Callback for when the user presses the button to toggle mute
+ const toggleMute = React.useCallback(() => {
+ setMuted((prev) => !prev);
+ }, [setMuted]);
- return (
-
-
- Robot audio playback is not supported on this browser
-
-
- {muted ? (
- volume_off
- ) : (
- volume_up
- )}
-
-
- );
+ return (
+
+
+ Robot audio playback is not supported on this browser
+
+
+ {muted ? (
+ volume_off
+ ) : (
+ volume_up
+ )}
+
+
+ );
};
diff --git a/src/pages/operator/tsx/static_components/BatteryGauge.tsx b/src/pages/operator/tsx/static_components/BatteryGauge.tsx
index e2233b9c..efb11234 100644
--- a/src/pages/operator/tsx/static_components/BatteryGauge.tsx
+++ b/src/pages/operator/tsx/static_components/BatteryGauge.tsx
@@ -8,21 +8,21 @@ import React from "react";
import { BatteryVoltageFunctions } from "../function_providers/BatteryVoltageFunctionProvider";
export const BatteryGuage = (props: CustomizableComponentProps) => {
- const [color, setColor] = useState("green");
+ const [color, setColor] = useState("green");
- batteryVoltageFunctionProvider.setVoltageChangeCallback(setColor);
+ batteryVoltageFunctionProvider.setVoltageChangeCallback(setColor);
- return (
-
-
- {/*
+ return (
+
+
+ {/*
*/}
-
Battery Gauge
-
- );
+
Battery Gauge
+
+ );
};
diff --git a/src/pages/operator/tsx/static_components/Canvas.tsx b/src/pages/operator/tsx/static_components/Canvas.tsx
index b36f74d5..b9170604 100644
--- a/src/pages/operator/tsx/static_components/Canvas.tsx
+++ b/src/pages/operator/tsx/static_components/Canvas.tsx
@@ -2,50 +2,50 @@ import React from "react";
import createjs from "createjs-module";
export class Canvas extends React.Component {
- private divID: string;
- private className: string;
- private width: number;
- private height: number;
- public scene?: createjs.Stage;
-
- constructor(props: {
- divID: string;
- className: string;
- width: number;
- height: number;
- }) {
- super(props);
- this.divID = props.divID;
- this.className = props.className;
- this.width = props.width;
- this.height = props.height;
- this.createCanvas();
- }
-
- createCanvas() {
- // create the canvas to render to
- var canvas = document.createElement("canvas");
- canvas.setAttribute("class", this.className);
- canvas.width = this.width;
- canvas.height = this.height;
-
- // create the easel to use
- this.scene = new createjs.Stage(canvas);
-
- // add the renderer to the page
- document.getElementById(this.divID)!.appendChild(canvas);
-
- // update at 30fps
- createjs.Ticker.framerate = 30;
- createjs.Ticker.addEventListener("tick", this.scene);
- }
-
- scaleToDimensions(width: number, height: number) {
- if (!this.scene) throw "Canvas scene is undefined!";
-
- // save scene scaling
- this.scene.scaleX = this.width / width;
- this.scene.scaleY = this.height / height;
- this.scene.update();
- }
+ private divID: string;
+ private className: string;
+ private width: number;
+ private height: number;
+ public scene?: createjs.Stage;
+
+ constructor(props: {
+ divID: string;
+ className: string;
+ width: number;
+ height: number;
+ }) {
+ super(props);
+ this.divID = props.divID;
+ this.className = props.className;
+ this.width = props.width;
+ this.height = props.height;
+ this.createCanvas();
+ }
+
+ createCanvas() {
+ // create the canvas to render to
+ var canvas = document.createElement("canvas");
+ canvas.setAttribute("class", this.className);
+ canvas.width = this.width;
+ canvas.height = this.height;
+
+ // create the easel to use
+ this.scene = new createjs.Stage(canvas);
+
+ // add the renderer to the page
+ document.getElementById(this.divID)!.appendChild(canvas);
+
+ // update at 30fps
+ createjs.Ticker.framerate = 30;
+ createjs.Ticker.addEventListener("tick", this.scene);
+ }
+
+ scaleToDimensions(width: number, height: number) {
+ if (!this.scene) throw "Canvas scene is undefined!";
+
+ // save scene scaling
+ this.scene.scaleX = this.width / width;
+ this.scene.scaleY = this.height / height;
+ this.scene.update();
+ }
}
diff --git a/src/pages/operator/tsx/static_components/CustomizeButton.tsx b/src/pages/operator/tsx/static_components/CustomizeButton.tsx
index 800d19c2..50cadca9 100644
--- a/src/pages/operator/tsx/static_components/CustomizeButton.tsx
+++ b/src/pages/operator/tsx/static_components/CustomizeButton.tsx
@@ -1,25 +1,27 @@
import "operator/css/CustomizeButton.css";
type CustomizeButtonProps = {
- /** If the interface is in customization mode */
- customizing: boolean;
- /** Callback for clicking the button */
- onClick: () => void;
+ /** If the interface is in customization mode */
+ customizing: boolean;
+ /** Callback for clicking the button */
+ onClick: () => void;
};
/** Button to toggle customization mode. */
export const CustomizeButton = (props: CustomizeButtonProps) => {
- const icon = props.customizing ? "check" : "build";
- const text = props.customizing ? "Done" : "Customize";
- return (
-
- {icon}
- {text}
-
- );
+ const icon = props.customizing ? "check" : "build";
+ const text = props.customizing ? "Done" : "Customize";
+ return (
+
+ {icon}
+ {text}
+
+ );
};
// Uses icons from https://fonts.google.com/icons
diff --git a/src/pages/operator/tsx/static_components/LayoutArea.tsx b/src/pages/operator/tsx/static_components/LayoutArea.tsx
index e04ed74f..40b7b8a4 100644
--- a/src/pages/operator/tsx/static_components/LayoutArea.tsx
+++ b/src/pages/operator/tsx/static_components/LayoutArea.tsx
@@ -1,66 +1,66 @@
import React from "react";
import {
- ComponentDefinition,
- LayoutDefinition,
- LayoutGridDefinition,
- PanelDefinition,
- ParentComponentDefinition,
+ ComponentDefinition,
+ LayoutDefinition,
+ LayoutGridDefinition,
+ PanelDefinition,
+ ParentComponentDefinition,
} from "operator/tsx/utils/component_definitions";
import { SharedState } from "../layout_components/CustomizableComponent";
import {
- ComponentList,
- ComponentListProps,
+ ComponentList,
+ ComponentListProps,
} from "../layout_components/ComponentList";
import "operator/css/LayoutArea.css";
import { DropZone } from "../layout_components/DropZone";
/** Properties for {@link LayoutArea} */
type LayoutAreaProps = {
- /** Layout structure to render */
- layout: LayoutDefinition;
- sharedState: SharedState;
+ /** Layout structure to render */
+ layout: LayoutDefinition;
+ sharedState: SharedState;
};
/** Main area of the interface where the user can add, remove, or rearrange elements. */
export const LayoutArea = (props: LayoutAreaProps) => {
- // const componentListProps: ComponentListProps = {
- // definition: props.layout,
- // path: "",
- // sharedState: props.sharedState
- // }
- const panelColumn = props.layout.children;
- const dropZoneIdx = 0;
- return (
- <>
- {panelColumn.map((compDef: LayoutGridDefinition, index: number) => {
- return (
- // compDef.children.length > 0 ?
- <>
+ // const componentListProps: ComponentListProps = {
+ // definition: props.layout,
+ // path: "",
+ // sharedState: props.sharedState
+ // }
+ const panelColumn = props.layout.children;
+ const dropZoneIdx = 0;
+ return (
+ <>
+ {panelColumn.map((compDef: LayoutGridDefinition, index: number) => {
+ return (
+ // compDef.children.length > 0 ?
+ <>
+
+
+
+
+ >
+ // :
+ // <>>
+ );
+ })}
-
-
-
- >
- // :
- // <>>
- );
- })}
-
- >
- );
+ >
+ );
};
diff --git a/src/pages/operator/tsx/static_components/OccupancyGrid.tsx b/src/pages/operator/tsx/static_components/OccupancyGrid.tsx
index 407cd3fa..6038d2e9 100644
--- a/src/pages/operator/tsx/static_components/OccupancyGrid.tsx
+++ b/src/pages/operator/tsx/static_components/OccupancyGrid.tsx
@@ -7,350 +7,369 @@ import ROSLIB from "roslib";
import { MapFunctions } from "../layout_components/Map";
export class OccupancyGrid extends React.Component {
- private rootObject: createjs.Stage;
- private origin?: ROSLIB.Pose;
- private bitmap?: createjs.Bitmap;
- public width: number;
- public height: number;
- private scaleX?: number;
- private scaleY?: number;
- private map: ROSOccupancyGrid;
- private goal_position?: ROSPoint;
- private goalMarker?: createjs.Shape;
- private getGoalReached?: NodeJS.Timer;
- private savedPoseMarkers: { circle: createjs.Shape; label: createjs.Text }[];
- private savedPoseMarkersLabels: string[];
- private functs: MapFunctions;
- constructor(props: { functs: MapFunctions; rootObject: createjs.Stage }) {
- super(props);
- this.rootObject = props.rootObject;
- this.rootObject.enableMouseOver();
- createjs.Touch.enable(this.rootObject);
- this.width = 0;
- this.height = 0;
- this.map = props.functs.GetMap;
- this.functs = props.functs;
- this.savedPoseMarkers = [];
- this.savedPoseMarkersLabels = [];
- this.createOccupancyGridClient();
- }
-
- drawSavedPoseMarker(x: number, y: number, color: number[], text: string) {
- var circle = new createjs.Shape();
- var radius = 30;
-
- var graphics = new createjs.Graphics();
- graphics.beginFill(
- createjs.Graphics.getRGB(color[0], color[1], color[2], 0.5),
- );
- graphics.drawCircle(0, 0, radius);
-
- createjs.Shape.call(circle, graphics);
-
- circle.x = x;
- circle.y = y;
- circle.scaleX = 1.0 / this.rootObject.scaleX;
- circle.scaleY = 1.0 / this.rootObject.scaleY;
-
- var label = new createjs.Text(text, "bold 40px Arial", "#ff7700");
- label.x = x;
- label.y = y - 10;
- label.textAlign = "center";
- label.scaleX = 1.0 / this.rootObject.scaleX;
- label.scaleY = 1.0 / this.rootObject.scaleY;
- label.textBaseline = "alphabetic";
-
- circle.on("mouseover", (event) => {
- label.visible = true;
- });
- circle.on("mouseout", (event) => {
- label.visible = false;
- });
- return { circle, label };
- }
-
- drawNavigationArrow(pulse: boolean, color: number[]) {
- var arrow = new createjs.Shape();
- var size = 40;
- var strokeSize = 0;
- var strokeColor = createjs.Graphics.getRGB(
- color[0],
- color[1],
- color[2],
- 0.7,
- );
- var fillColor = createjs.Graphics.getRGB(color[0], color[1], color[2], 0.7);
-
- // draw the arrow
- var graphics = new createjs.Graphics();
-
- // line width
- graphics.setStrokeStyle(strokeSize);
- graphics.moveTo(0.0, size / 1.5);
- graphics.beginStroke(strokeColor);
- graphics.beginFill(fillColor);
- graphics.lineTo(-size / 2.0, -size / 2.0);
- graphics.lineTo(size / 2.0, -size / 2.0);
- graphics.lineTo(0.0, size / 1.5);
- graphics.closePath();
- graphics.endFill();
- graphics.endStroke();
-
- // create the shape
- createjs.Shape.call(arrow, graphics);
-
- // check if we are pulsing
- if (pulse) {
- // have the model "pulse"
- var growCount = 0;
- var growing = true;
- createjs.Ticker.addEventListener("tick", () => {
- if (growing) {
- arrow.scaleX *= 1.035;
- arrow.scaleY *= 1.035;
- growing = ++growCount < 10;
- } else {
- arrow.scaleX /= 1.035;
- arrow.scaleY /= 1.035;
- growing = --growCount < 0;
- }
- });
+ private rootObject: createjs.Stage;
+ private origin?: ROSLIB.Pose;
+ private bitmap?: createjs.Bitmap;
+ public width: number;
+ public height: number;
+ private scaleX?: number;
+ private scaleY?: number;
+ private map: ROSOccupancyGrid;
+ private goal_position?: ROSPoint;
+ private goalMarker?: createjs.Shape;
+ private getGoalReached?: NodeJS.Timer;
+ private savedPoseMarkers: {
+ circle: createjs.Shape;
+ label: createjs.Text;
+ }[];
+ private savedPoseMarkersLabels: string[];
+ private functs: MapFunctions;
+ constructor(props: { functs: MapFunctions; rootObject: createjs.Stage }) {
+ super(props);
+ this.rootObject = props.rootObject;
+ this.rootObject.enableMouseOver();
+ createjs.Touch.enable(this.rootObject);
+ this.width = 0;
+ this.height = 0;
+ this.map = props.functs.GetMap;
+ this.functs = props.functs;
+ this.savedPoseMarkers = [];
+ this.savedPoseMarkersLabels = [];
+ this.createOccupancyGridClient();
}
- return arrow;
- }
-
- createOccupancyGrid() {
- // internal drawing canvas
- var canvas = document.createElement("canvas");
- var context = canvas!.getContext("2d", { willReadFrequently: true });
-
- if (!this.map) {
- var rect = new createjs.Shape();
- rect.graphics.beginStroke("#000000");
- rect.graphics.setStrokeStyle(3);
- rect.graphics.drawRect(0, 0, 300, 500);
- rect.graphics.endStroke();
- var text = new createjs.Text("Could not load map", "30px Arial");
- text.x = 20;
- text.y = 250;
- this.rootObject.addChild(rect);
- this.rootObject.addChild(text);
- return;
+
+ drawSavedPoseMarker(x: number, y: number, color: number[], text: string) {
+ var circle = new createjs.Shape();
+ var radius = 30;
+
+ var graphics = new createjs.Graphics();
+ graphics.beginFill(
+ createjs.Graphics.getRGB(color[0], color[1], color[2], 0.5),
+ );
+ graphics.drawCircle(0, 0, radius);
+
+ createjs.Shape.call(circle, graphics);
+
+ circle.x = x;
+ circle.y = y;
+ circle.scaleX = 1.0 / this.rootObject.scaleX;
+ circle.scaleY = 1.0 / this.rootObject.scaleY;
+
+ var label = new createjs.Text(text, "bold 40px Arial", "#ff7700");
+ label.x = x;
+ label.y = y - 10;
+ label.textAlign = "center";
+ label.scaleX = 1.0 / this.rootObject.scaleX;
+ label.scaleY = 1.0 / this.rootObject.scaleY;
+ label.textBaseline = "alphabetic";
+
+ circle.on("mouseover", (event) => {
+ label.visible = true;
+ });
+ circle.on("mouseout", (event) => {
+ label.visible = false;
+ });
+ return { circle, label };
}
- // save the metadata we need
- this.origin = new ROSLIB.Pose({
- position: this.map.info.origin.position,
- orientation: this.map.info.origin.orientation,
- });
- // set the size
- this.width = this.map.info.width;
- this.height = this.map.info.height;
- canvas.width = this.width;
- canvas.height = this.height;
-
- var imageData = context!.createImageData(this.width, this.height);
- for (var row = 0; row < this.height; row++) {
- for (var col = 0; col < this.width; col++) {
- // determine the index into the map data
- var mapI = col + (this.height - row - 1) * this.width;
- // determine the value
- var data = this.map.data[mapI];
- var val;
- if (data === 100) {
- val = 0;
- } else if (data === 0) {
- val = 255;
- } else {
- val = 127;
+ drawNavigationArrow(pulse: boolean, color: number[]) {
+ var arrow = new createjs.Shape();
+ var size = 40;
+ var strokeSize = 0;
+ var strokeColor = createjs.Graphics.getRGB(
+ color[0],
+ color[1],
+ color[2],
+ 0.7,
+ );
+ var fillColor = createjs.Graphics.getRGB(
+ color[0],
+ color[1],
+ color[2],
+ 0.7,
+ );
+
+ // draw the arrow
+ var graphics = new createjs.Graphics();
+
+ // line width
+ graphics.setStrokeStyle(strokeSize);
+ graphics.moveTo(0.0, size / 1.5);
+ graphics.beginStroke(strokeColor);
+ graphics.beginFill(fillColor);
+ graphics.lineTo(-size / 2.0, -size / 2.0);
+ graphics.lineTo(size / 2.0, -size / 2.0);
+ graphics.lineTo(0.0, size / 1.5);
+ graphics.closePath();
+ graphics.endFill();
+ graphics.endStroke();
+
+ // create the shape
+ createjs.Shape.call(arrow, graphics);
+
+ // check if we are pulsing
+ if (pulse) {
+ // have the model "pulse"
+ var growCount = 0;
+ var growing = true;
+ createjs.Ticker.addEventListener("tick", () => {
+ if (growing) {
+ arrow.scaleX *= 1.035;
+ arrow.scaleY *= 1.035;
+ growing = ++growCount < 10;
+ } else {
+ arrow.scaleX /= 1.035;
+ arrow.scaleY /= 1.035;
+ growing = --growCount < 0;
+ }
+ });
+ }
+ return arrow;
+ }
+
+ createOccupancyGrid() {
+ // internal drawing canvas
+ var canvas = document.createElement("canvas");
+ var context = canvas!.getContext("2d", { willReadFrequently: true });
+
+ if (!this.map) {
+ var rect = new createjs.Shape();
+ rect.graphics.beginStroke("#000000");
+ rect.graphics.setStrokeStyle(3);
+ rect.graphics.drawRect(0, 0, 300, 500);
+ rect.graphics.endStroke();
+ var text = new createjs.Text("Could not load map", "30px Arial");
+ text.x = 20;
+ text.y = 250;
+ this.rootObject.addChild(rect);
+ this.rootObject.addChild(text);
+ return;
}
- // determine the index into the image data array
- var i = (col + row * this.width) * 4;
- // r
- imageData.data[i] = val;
- // g
- imageData.data[++i] = val;
- // b
- imageData.data[++i] = val;
- // a
- imageData.data[++i] = 255;
- }
+
+ // save the metadata we need
+ this.origin = new ROSLIB.Pose({
+ position: this.map.info.origin.position,
+ orientation: this.map.info.origin.orientation,
+ });
+ // set the size
+ this.width = this.map.info.width;
+ this.height = this.map.info.height;
+ canvas.width = this.width;
+ canvas.height = this.height;
+
+ var imageData = context!.createImageData(this.width, this.height);
+ for (var row = 0; row < this.height; row++) {
+ for (var col = 0; col < this.width; col++) {
+ // determine the index into the map data
+ var mapI = col + (this.height - row - 1) * this.width;
+ // determine the value
+ var data = this.map.data[mapI];
+ var val;
+ if (data === 100) {
+ val = 0;
+ } else if (data === 0) {
+ val = 255;
+ } else {
+ val = 127;
+ }
+ // determine the index into the image data array
+ var i = (col + row * this.width) * 4;
+ // r
+ imageData.data[i] = val;
+ // g
+ imageData.data[++i] = val;
+ // b
+ imageData.data[++i] = val;
+ // a
+ imageData.data[++i] = 255;
+ }
+ }
+
+ context!.putImageData(imageData, 0, 0);
+
+ // create the bitmap
+ this.bitmap = new createjs.Bitmap(canvas);
+ this.rootObject.addChild(this.bitmap);
+
+ // scale the image
+ this.scaleX = this.map.info.resolution;
+ this.scaleY = this.map.info.resolution;
+ }
+
+ rosToGlobal(translation: ROSLIB.Vector3) {
+ var x =
+ (this.width * this.scaleX! -
+ (-translation.x +
+ this.width * this.scaleX! +
+ this.origin!.position.x)) /
+ this.scaleX!;
+ var y =
+ (-translation.y +
+ this.height * this.scaleY! +
+ this.origin!.position.y) /
+ this.scaleY!;
+ return {
+ x: x,
+ y: y,
+ };
}
- context!.putImageData(imageData, 0, 0);
-
- // create the bitmap
- this.bitmap = new createjs.Bitmap(canvas);
- this.rootObject.addChild(this.bitmap);
-
- // scale the image
- this.scaleX = this.map.info.resolution;
- this.scaleY = this.map.info.resolution;
- }
-
- rosToGlobal(translation: ROSLIB.Vector3) {
- var x =
- (this.width * this.scaleX! -
- (-translation.x +
- this.width * this.scaleX! +
- this.origin!.position.x)) /
- this.scaleX!;
- var y =
- (-translation.y + this.height * this.scaleY! + this.origin!.position.y) /
- this.scaleY!;
- return {
- x: x,
- y: y,
- };
- }
-
- // https://github.com/RobotWebTools/ros2djs/blob/develop/src/Ros2D.js#L34C1-L44C3
- // convert a ROS quaternion to theta in degrees
- rosQuaternionToGlobalTheta(orientation: ROSLIB.Quaternion) {
- // See https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Rotation_matrices
- // here we use [x y z] = R * [1 0 0]
- var w = orientation.w;
- var x = orientation.x;
- var y = orientation.y;
- var z = orientation.z;
- // Canvas rotation is clock wise and in degrees
- return (
- (-Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) * 180.0) /
- Math.PI
- );
- }
-
- globalToRos(x: number, y: number) {
- var rosX = (x / 5) * this.scaleX! + this.origin!.position.x;
- var rosY = (this.height - y / 5) * this.scaleY! + this.origin!.position.y;
- console.log(rosX, rosY);
- return {
- x: rosX,
- y: rosY,
- z: 0,
- } as ROSPoint;
- }
-
- addCurrenPoseMarker() {
- var robotMarker = this.drawNavigationArrow(false, [255, 128, 0]);
- this.rootObject.addChild(robotMarker);
-
- const setPoseInterval = setInterval(() => {
- let pose = this.functs.GetPose();
- let globalCoord = this.rosToGlobal(pose.translation);
- robotMarker.x = globalCoord.x;
- robotMarker.y = globalCoord.y;
- let theta = this.rosQuaternionToGlobalTheta(pose.rotation);
- robotMarker.rotation = theta - 90.0;
- robotMarker.scaleX = 1.0 / this.rootObject.scaleX;
- robotMarker.scaleY = 1.0 / this.rootObject.scaleY;
- robotMarker.visible = true;
- this.rootObject.update();
- }, 1000);
- }
-
- public displayPoseMarkers(
- display: boolean,
- poses: ROSLIB.Transform[],
- poseNames: string[],
- poseTypes: string[],
- ) {
- if (!display) {
- this.savedPoseMarkers.forEach((marker) => {
- marker.circle.visible = false;
- marker.label.visible = false;
- });
- } else {
- // Re-draw or add pose markers
- poses.forEach((pose, index) => {
- // Recreate marker
- let globalCoord = this.rosToGlobal(pose.translation);
- let color = poseTypes[index] == "MAP" ? [0, 0, 255] : [255, 0, 0];
- var poseMarker = this.drawSavedPoseMarker(
- globalCoord.x,
- globalCoord.y,
- color,
- poseNames[index],
+ // https://github.com/RobotWebTools/ros2djs/blob/develop/src/Ros2D.js#L34C1-L44C3
+ // convert a ROS quaternion to theta in degrees
+ rosQuaternionToGlobalTheta(orientation: ROSLIB.Quaternion) {
+ // See https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Rotation_matrices
+ // here we use [x y z] = R * [1 0 0]
+ var w = orientation.w;
+ var x = orientation.x;
+ var y = orientation.y;
+ var z = orientation.z;
+ // Canvas rotation is clock wise and in degrees
+ return (
+ (-Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) *
+ 180.0) /
+ Math.PI
);
- poseMarker.circle.visible = true;
- poseMarker.label.visible = false;
-
- var label_idx = this.savedPoseMarkersLabels.indexOf(poseNames[index]);
- // If old pose marker label exists, overwrite marker
- if (label_idx !== -1) {
- var oldPoseMarker = this.savedPoseMarkers[label_idx];
- this.rootObject.removeChild(oldPoseMarker.circle);
- this.rootObject.removeChild(oldPoseMarker.label);
- this.savedPoseMarkers[label_idx] = poseMarker;
- this.savedPoseMarkersLabels[label_idx] = poseNames[index];
+ }
+
+ globalToRos(x: number, y: number) {
+ var rosX = (x / 5) * this.scaleX! + this.origin!.position.x;
+ var rosY =
+ (this.height - y / 5) * this.scaleY! + this.origin!.position.y;
+ console.log(rosX, rosY);
+ return {
+ x: rosX,
+ y: rosY,
+ z: 0,
+ } as ROSPoint;
+ }
+
+ addCurrenPoseMarker() {
+ var robotMarker = this.drawNavigationArrow(false, [255, 128, 0]);
+ this.rootObject.addChild(robotMarker);
+
+ const setPoseInterval = setInterval(() => {
+ let pose = this.functs.GetPose();
+ let globalCoord = this.rosToGlobal(pose.translation);
+ robotMarker.x = globalCoord.x;
+ robotMarker.y = globalCoord.y;
+ let theta = this.rosQuaternionToGlobalTheta(pose.rotation);
+ robotMarker.rotation = theta - 90.0;
+ robotMarker.scaleX = 1.0 / this.rootObject.scaleX;
+ robotMarker.scaleY = 1.0 / this.rootObject.scaleY;
+ robotMarker.visible = true;
+ this.rootObject.update();
+ }, 1000);
+ }
+
+ public displayPoseMarkers(
+ display: boolean,
+ poses: ROSLIB.Transform[],
+ poseNames: string[],
+ poseTypes: string[],
+ ) {
+ if (!display) {
+ this.savedPoseMarkers.forEach((marker) => {
+ marker.circle.visible = false;
+ marker.label.visible = false;
+ });
} else {
- this.savedPoseMarkers.push(poseMarker);
- this.savedPoseMarkersLabels.push(poseNames[index]);
+ // Re-draw or add pose markers
+ poses.forEach((pose, index) => {
+ // Recreate marker
+ let globalCoord = this.rosToGlobal(pose.translation);
+ let color =
+ poseTypes[index] == "MAP" ? [0, 0, 255] : [255, 0, 0];
+ var poseMarker = this.drawSavedPoseMarker(
+ globalCoord.x,
+ globalCoord.y,
+ color,
+ poseNames[index],
+ );
+ poseMarker.circle.visible = true;
+ poseMarker.label.visible = false;
+
+ var label_idx = this.savedPoseMarkersLabels.indexOf(
+ poseNames[index],
+ );
+ // If old pose marker label exists, overwrite marker
+ if (label_idx !== -1) {
+ var oldPoseMarker = this.savedPoseMarkers[label_idx];
+ this.rootObject.removeChild(oldPoseMarker.circle);
+ this.rootObject.removeChild(oldPoseMarker.label);
+ this.savedPoseMarkers[label_idx] = poseMarker;
+ this.savedPoseMarkersLabels[label_idx] = poseNames[index];
+ } else {
+ this.savedPoseMarkers.push(poseMarker);
+ this.savedPoseMarkersLabels.push(poseNames[index]);
+ }
+
+ this.rootObject.addChild(poseMarker.circle);
+ this.rootObject.addChild(poseMarker.label);
+ });
}
+ this.rootObject.update();
+ }
- this.rootObject.addChild(poseMarker.circle);
- this.rootObject.addChild(poseMarker.label);
- });
+ public createGoalMarker(x: number, y: number, ros: boolean) {
+ let globalCoord = { x: x, y: y };
+ if (ros)
+ globalCoord = this.rosToGlobal({
+ x: x,
+ y: y,
+ z: 0,
+ } as ROSLIB.Vector3);
+ if (this.getGoalReached) clearInterval(this.getGoalReached);
+ if (this.goalMarker) this.rootObject.removeChild(this.goalMarker);
+ this.goalMarker = this.drawNavigationArrow(true, [255, 0, 0]);
+ this.goalMarker.x = globalCoord.x;
+ this.goalMarker.y = globalCoord.y;
+ this.goalMarker.scaleX = 1.0 / this.rootObject.scaleX;
+ this.goalMarker.scaleY = 1.0 / this.rootObject.scaleY;
+ this.goalMarker.visible = true;
+ this.rootObject.addChild(this.goalMarker);
+
+ this.getGoalReached = setInterval(() => {
+ if (this.functs.GoalReached()) {
+ this.rootObject.removeChild(this.goalMarker!);
+ clearInterval(this.getGoalReached);
+ }
+ }, 1000);
}
- this.rootObject.update();
- }
-
- public createGoalMarker(x: number, y: number, ros: boolean) {
- let globalCoord = { x: x, y: y };
- if (ros)
- globalCoord = this.rosToGlobal({ x: x, y: y, z: 0 } as ROSLIB.Vector3);
- if (this.getGoalReached) clearInterval(this.getGoalReached);
- if (this.goalMarker) this.rootObject.removeChild(this.goalMarker);
- this.goalMarker = this.drawNavigationArrow(true, [255, 0, 0]);
- this.goalMarker.x = globalCoord.x;
- this.goalMarker.y = globalCoord.y;
- this.goalMarker.scaleX = 1.0 / this.rootObject.scaleX;
- this.goalMarker.scaleY = 1.0 / this.rootObject.scaleY;
- this.goalMarker.visible = true;
- this.rootObject.addChild(this.goalMarker);
-
- this.getGoalReached = setInterval(() => {
- if (this.functs.GoalReached()) {
- this.rootObject.removeChild(this.goalMarker!);
- clearInterval(this.getGoalReached);
- }
- }, 1000);
- }
-
- play() {
- if (this.goal_position) {
- this.functs.MoveBase({
- position: this.goal_position,
- orientation: { x: 0, y: 0, z: -0.45, w: 0.893 },
- } as ROSPose);
+
+ play() {
+ if (this.goal_position) {
+ this.functs.MoveBase({
+ position: this.goal_position,
+ orientation: { x: 0, y: 0, z: -0.45, w: 0.893 },
+ } as ROSPose);
+ }
+ this.goal_position = undefined;
+ // if (isMobile) this.functs.SetSelectGoal(false)
+ this.functs.SetSelectGoal(false);
}
- this.goal_position = undefined;
- // if (isMobile) this.functs.SetSelectGoal(false)
- this.functs.SetSelectGoal(false);
- }
- removeGoalMarker() {
- console.log("removing");
- this.goal_position = undefined;
- if (this.goalMarker) this.rootObject.removeChild(this.goalMarker);
- }
+ removeGoalMarker() {
+ console.log("removing");
+ this.goal_position = undefined;
+ if (this.goalMarker) this.rootObject.removeChild(this.goalMarker);
+ }
- createOccupancyGridClient() {
- this.createOccupancyGrid();
+ createOccupancyGridClient() {
+ this.createOccupancyGrid();
- if (!this.map) return;
+ if (!this.map) return;
- this.addCurrenPoseMarker();
+ this.addCurrenPoseMarker();
- this.rootObject.on("mousedown", (event) => {
- let evt = event as createjs.MouseEvent;
- // convert to ROS coordinates
- this.goal_position = this.globalToRos(evt.stageX, evt.stageY);
+ this.rootObject.on("mousedown", (event) => {
+ let evt = event as createjs.MouseEvent;
+ // convert to ROS coordinates
+ this.goal_position = this.globalToRos(evt.stageX, evt.stageY);
- if (this.functs.SelectGoal()) {
- this.createGoalMarker(evt.stageX / 5, evt.stageY / 5, false);
- this.functs.SetSelectGoal(false);
- }
- });
- }
+ if (this.functs.SelectGoal()) {
+ this.createGoalMarker(evt.stageX / 5, evt.stageY / 5, false);
+ this.functs.SetSelectGoal(false);
+ }
+ });
+ }
}
diff --git a/src/pages/operator/tsx/static_components/RunStop.tsx b/src/pages/operator/tsx/static_components/RunStop.tsx
index 08c970ce..ad8aea30 100644
--- a/src/pages/operator/tsx/static_components/RunStop.tsx
+++ b/src/pages/operator/tsx/static_components/RunStop.tsx
@@ -7,23 +7,23 @@ import runStopButton from "operator/icons/button.svg";
import React, { useState } from "react";
export const RunStopButton = (props: CustomizableComponentProps) => {
- const functs: RunStopFunctions = runStopFunctionProvider.provideFunctions();
- const [enabled, setEnabled] = useState
(false);
+ const functs: RunStopFunctions = runStopFunctionProvider.provideFunctions();
+ const [enabled, setEnabled] = useState(false);
- runStopFunctionProvider.setRunStopStateChangeCallback(setEnabled);
+ runStopFunctionProvider.setRunStopStateChangeCallback(setEnabled);
- return (
-
-
- {enabled ? (
-
Run Stop: Enabled
- ) : (
-
Run Stop: Disabled
- )}
-
- );
+ return (
+
+
+ {enabled ? (
+
Run Stop: Enabled
+ ) : (
+
Run Stop: Disabled
+ )}
+
+ );
};
diff --git a/src/pages/operator/tsx/static_components/Sidebar.tsx b/src/pages/operator/tsx/static_components/Sidebar.tsx
index b53a834d..c50952eb 100644
--- a/src/pages/operator/tsx/static_components/Sidebar.tsx
+++ b/src/pages/operator/tsx/static_components/Sidebar.tsx
@@ -1,18 +1,18 @@
import React from "react";
import { className } from "shared/util";
import {
- ButtonPadDefinition,
- ButtonPadId,
- ComponentDefinition,
- ComponentId,
- ComponentType,
- LayoutDefinition,
- ParentComponentDefinition,
- TabDefinition,
- PanelDefinition,
- CameraViewDefinition,
- CameraViewId,
- MapDefinition,
+ ButtonPadDefinition,
+ ButtonPadId,
+ ComponentDefinition,
+ ComponentId,
+ ComponentType,
+ LayoutDefinition,
+ ParentComponentDefinition,
+ TabDefinition,
+ PanelDefinition,
+ CameraViewDefinition,
+ CameraViewId,
+ MapDefinition,
} from "../utils/component_definitions";
import { PopupModal } from "../basic_components/PopupModal";
import { Dropdown } from "../basic_components/Dropdown";
@@ -20,58 +20,60 @@ import "operator/css/Sidebar.css";
import { storageHandler } from "operator/tsx/index";
type SidebarProps = {
- hidden: boolean;
- onDelete: () => void;
- updateLayout: () => void;
- onSelect: (def: ComponentDefinition, path?: string) => void;
- selectedDefinition?: ComponentDefinition;
- selectedPath?: string;
- globalOptionsProps: GlobalOptionsProps;
+ hidden: boolean;
+ onDelete: () => void;
+ updateLayout: () => void;
+ onSelect: (def: ComponentDefinition, path?: string) => void;
+ selectedDefinition?: ComponentDefinition;
+ selectedPath?: string;
+ globalOptionsProps: GlobalOptionsProps;
};
/** Popup on the right side of the screen while in customization mode. */
export const Sidebar = (props: SidebarProps) => {
- const deleteDisabled = props.selectedPath === undefined;
- const deleteTooltip = deleteDisabled
- ? "You must select an element before you can delete it"
- : "";
- const selectedDescription = props.selectedDefinition
- ? componentDescription(props.selectedDefinition)
- : "none";
- return (
-
- );
+ const deleteDisabled = props.selectedPath === undefined;
+ const deleteTooltip = deleteDisabled
+ ? "You must select an element before you can delete it"
+ : "";
+ const selectedDescription = props.selectedDefinition
+ ? componentDescription(props.selectedDefinition)
+ : "none";
+ return (
+
+ );
};
/**
@@ -80,22 +82,22 @@ export const Sidebar = (props: SidebarProps) => {
* @returns string description of the component
*/
function componentDescription(definition: ComponentDefinition): string {
- switch (definition.type) {
- case ComponentType.ButtonPad:
- case ComponentType.CameraView:
- return `${(definition as CameraViewDefinition | ButtonPadDefinition).id} ${definition.type}`;
- case ComponentType.SingleTab:
- return `\"${(definition as TabDefinition).label}\" Tab`;
- case ComponentType.Panel:
- case ComponentType.VirtualJoystick:
- case ComponentType.ButtonGrid:
- case ComponentType.Map:
- return definition.type;
- default:
- throw Error(
- `Cannot get description for component type ${definition.type}\nYou may need to add a case for this component in the switch statement.`,
- );
- }
+ switch (definition.type) {
+ case ComponentType.ButtonPad:
+ case ComponentType.CameraView:
+ return `${(definition as CameraViewDefinition | ButtonPadDefinition).id} ${definition.type}`;
+ case ComponentType.SingleTab:
+ return `\"${(definition as TabDefinition).label}\" Tab`;
+ case ComponentType.Panel:
+ case ComponentType.VirtualJoystick:
+ case ComponentType.ButtonGrid:
+ case ComponentType.Map:
+ return definition.type;
+ default:
+ throw Error(
+ `Cannot get description for component type ${definition.type}\nYou may need to add a case for this component in the switch statement.`,
+ );
+ }
}
/*******************************************************************************
@@ -104,191 +106,193 @@ function componentDescription(definition: ComponentDefinition): string {
/** Properties for {@link SidebarGlobalOptions} */
export type GlobalOptionsProps = {
- /** If the save/load poses should be displayed. */
- displayMovementRecorder: boolean;
- setDisplayMovementRecorder: (displayMovementRecorder: boolean) => void;
-
- /** If the text-to-speech component should be displayed */
- displayTextToSpeech: boolean;
- setDisplayTextToSpeech: (displayTextToSpeech: boolean) => void;
-
- /** If the button text labels should be displayed */
- displayLabels: boolean;
- setDisplayLabels: (displayLabels: boolean) => void;
-
- /** List of names of the default layouts. */
- defaultLayouts: string[];
- /** List of names of the user's custom layouts. */
- customLayouts: string[];
- /**
- * Callback when the user loads a layout.
- * @param layoutName name of the layout to load
- * @param dflt if it's a default layout, if false then it's a custom layout.
- */
- loadLayout: (layoutName: string, dflt: boolean) => void;
- /**
- * Callback when the user saves a layout.
- * @param layoutName name of the layout to save.
- */
- saveLayout: (layoutName: string) => void;
+ /** If the save/load poses should be displayed. */
+ displayMovementRecorder: boolean;
+ setDisplayMovementRecorder: (displayMovementRecorder: boolean) => void;
+
+ /** If the text-to-speech component should be displayed */
+ displayTextToSpeech: boolean;
+ setDisplayTextToSpeech: (displayTextToSpeech: boolean) => void;
+
+ /** If the button text labels should be displayed */
+ displayLabels: boolean;
+ setDisplayLabels: (displayLabels: boolean) => void;
+
+ /** List of names of the default layouts. */
+ defaultLayouts: string[];
+ /** List of names of the user's custom layouts. */
+ customLayouts: string[];
+ /**
+ * Callback when the user loads a layout.
+ * @param layoutName name of the layout to load
+ * @param dflt if it's a default layout, if false then it's a custom layout.
+ */
+ loadLayout: (layoutName: string, dflt: boolean) => void;
+ /**
+ * Callback when the user saves a layout.
+ * @param layoutName name of the layout to save.
+ */
+ saveLayout: (layoutName: string) => void;
};
/** Options which apply to the entire operator page. */
const SidebarGlobalOptions = (props: GlobalOptionsProps) => {
- const [showLoadLayoutModal, setShowLoadLayoutModal] =
- React.useState(false);
- const [showSaveLayoutModal, setShowSaveLayoutModal] =
- React.useState(false);
-
- return (
-
-
- {/*
Global settings:
*/}
-
props.setDisplayLabels(!props.displayLabels)}
- label="Display button text labels"
- />
-
- props.setDisplayMovementRecorder(!props.displayMovementRecorder)
- }
- label="Display movement recorder"
- />
-
- props.setDisplayTextToSpeech(!props.displayTextToSpeech)
- }
- label="Display text-to-speech"
- />
- setShowLoadLayoutModal(true)}>
- Load layout
-
- setShowSaveLayoutModal(true)}>
- Save layout
-
-
-
-
-
- );
+ const [showLoadLayoutModal, setShowLoadLayoutModal] =
+ React.useState(false);
+ const [showSaveLayoutModal, setShowSaveLayoutModal] =
+ React.useState(false);
+
+ return (
+
+
+ {/*
Global settings:
*/}
+
props.setDisplayLabels(!props.displayLabels)}
+ label="Display button text labels"
+ />
+
+ props.setDisplayMovementRecorder(
+ !props.displayMovementRecorder,
+ )
+ }
+ label="Display movement recorder"
+ />
+
+ props.setDisplayTextToSpeech(!props.displayTextToSpeech)
+ }
+ label="Display text-to-speech"
+ />
+ setShowLoadLayoutModal(true)}>
+ Load layout
+
+ setShowSaveLayoutModal(true)}>
+ Save layout
+
+
+
+
+
+ );
};
/** Popup so the user can load a default layout or one of their custom layouts. */
const LoadLayoutModal = (props: {
- defaultLayouts: string[];
- customLayouts: string[];
- loadLayout: (layoutName: string, dflt: boolean) => void;
- setShow: (show: boolean) => void;
- show: boolean;
+ defaultLayouts: string[];
+ customLayouts: string[];
+ loadLayout: (layoutName: string, dflt: boolean) => void;
+ setShow: (show: boolean) => void;
+ show: boolean;
}) => {
- const [selectedIdx, setSelectedIdx] = React.useState();
-
- function handleAccept() {
- if (selectedIdx === undefined) return;
- let dflt: boolean, layoutName: string;
- if (selectedIdx < props.defaultLayouts.length) {
- layoutName = props.defaultLayouts[selectedIdx];
- dflt = true;
- } else {
- layoutName =
- props.customLayouts[selectedIdx - props.defaultLayouts.length];
- dflt = false;
+ const [selectedIdx, setSelectedIdx] = React.useState();
+
+ function handleAccept() {
+ if (selectedIdx === undefined) return;
+ let dflt: boolean, layoutName: string;
+ if (selectedIdx < props.defaultLayouts.length) {
+ layoutName = props.defaultLayouts[selectedIdx];
+ dflt = true;
+ } else {
+ layoutName =
+ props.customLayouts[selectedIdx - props.defaultLayouts.length];
+ dflt = false;
+ }
+ console.log("loading layout", layoutName, dflt);
+ props.loadLayout(layoutName, dflt);
}
- console.log("loading layout", layoutName, dflt);
- props.loadLayout(layoutName, dflt);
- }
- function mapFunct(layoutName: string, dflt: boolean) {
- const prefix = dflt ? "DEFAULT" : "CUSTOM";
+ function mapFunct(layoutName: string, dflt: boolean) {
+ const prefix = dflt ? "DEFAULT" : "CUSTOM";
+ return (
+
+ {prefix} {layoutName}
+
+ );
+ }
+
+ const defaultOptions = props.defaultLayouts.map((layoutName) =>
+ mapFunct(layoutName, true),
+ );
+ const customOptions = props.customLayouts.map((layoutName) =>
+ mapFunct(layoutName, false),
+ );
+
return (
-
- {prefix} {layoutName}
-
+
+
+ Load Layout
+
+
+
);
- }
-
- const defaultOptions = props.defaultLayouts.map((layoutName) =>
- mapFunct(layoutName, true),
- );
- const customOptions = props.customLayouts.map((layoutName) =>
- mapFunct(layoutName, false),
- );
-
- return (
-
-
- Load Layout
-
-
-
- );
};
/** Popup so the user can save their current layout. */
const SaveLayoutModal = (props: {
- saveLayout: (layoutName: string) => void;
- customLayouts: string[];
- setShow: (show: boolean) => void;
- show: boolean;
+ saveLayout: (layoutName: string) => void;
+ customLayouts: string[];
+ setShow: (show: boolean) => void;
+ show: boolean;
}) => {
- const [name, setName] = React.useState("");
- function handleAccept() {
- if (name.length > 0) {
- props.saveLayout(name);
- props.customLayouts.push(name);
+ const [name, setName] = React.useState("");
+ function handleAccept() {
+ if (name.length > 0) {
+ props.saveLayout(name);
+ props.customLayouts.push(name);
+ }
+ setName("");
}
- setName("");
- }
- return (
-
-
- Save Layout
-
- setName(e.target.value)}
- placeholder="Name for this layout"
- />
-
- );
+ return (
+
+
+ Save Layout
+
+ setName(e.target.value)}
+ placeholder="Name for this layout"
+ />
+
+ );
};
/*******************************************************************************
@@ -296,161 +300,165 @@ const SaveLayoutModal = (props: {
*/
type OptionsProps = {
- /** Definition of the currently selected component from operator. */
- selectedDefinition: ComponentDefinition;
- /** Callback to rerender the layout in operator. */
- updateLayout: () => void;
+ /** Definition of the currently selected component from operator. */
+ selectedDefinition: ComponentDefinition;
+ /** Callback to rerender the layout in operator. */
+ updateLayout: () => void;
};
/** Displays options for the currently selected layout component. */
const SidebarOptions = (props: OptionsProps) => {
- let contents: JSX.Element | null = null;
- switch (props.selectedDefinition.type) {
- case ComponentType.CameraView:
- switch ((props.selectedDefinition as CameraViewDefinition).id!) {
- case CameraViewId.overhead:
- contents = ;
- break;
- case CameraViewId.realsense:
- contents = ;
- break;
- case CameraViewId.gripper:
- contents = ;
- break;
- }
- break;
- case ComponentType.SingleTab:
- contents = ;
- }
- return ;
+ let contents: JSX.Element | null = null;
+ switch (props.selectedDefinition.type) {
+ case ComponentType.CameraView:
+ switch ((props.selectedDefinition as CameraViewDefinition).id!) {
+ case CameraViewId.overhead:
+ contents = ;
+ break;
+ case CameraViewId.realsense:
+ contents = ;
+ break;
+ case CameraViewId.gripper:
+ contents = ;
+ break;
+ }
+ break;
+ case ComponentType.SingleTab:
+ contents = ;
+ }
+ return ;
};
/** Options for the overhead camera video stream layout component. */
const OverheadVideoStreamOptions = (props: OptionsProps) => {
- const definition = props.selectedDefinition as CameraViewDefinition;
- const pd =
- definition.children.length > 0 &&
- definition.children[0].type == ComponentType.PredictiveDisplay;
- const [predictiveDisplayOn, setPredictiveDisplayOn] = React.useState(pd);
- const [showButtons, setShowButtons] = React.useState(true);
-
- function togglePredictiveDisplay() {
- const newPdOn = !predictiveDisplayOn;
- setPredictiveDisplayOn(newPdOn);
- if (newPdOn) {
- // Add predictive display to the stream
- definition.children = [{ type: ComponentType.PredictiveDisplay }];
- } else {
- definition.children = [];
+ const definition = props.selectedDefinition as CameraViewDefinition;
+ const pd =
+ definition.children.length > 0 &&
+ definition.children[0].type == ComponentType.PredictiveDisplay;
+ const [predictiveDisplayOn, setPredictiveDisplayOn] = React.useState(pd);
+ const [showButtons, setShowButtons] = React.useState(true);
+
+ function togglePredictiveDisplay() {
+ const newPdOn = !predictiveDisplayOn;
+ setPredictiveDisplayOn(newPdOn);
+ if (newPdOn) {
+ // Add predictive display to the stream
+ definition.children = [{ type: ComponentType.PredictiveDisplay }];
+ } else {
+ definition.children = [];
+ }
+ props.updateLayout();
+ }
+
+ function toggleButtons() {
+ setShowButtons(!showButtons);
+ definition.displayButtons = showButtons;
+ props.updateLayout();
}
- props.updateLayout();
- }
-
- function toggleButtons() {
- setShowButtons(!showButtons);
- definition.displayButtons = showButtons;
- props.updateLayout();
- }
-
- return (
-
- {/*
+ {/* */}
-
-
- );
+
+
+ );
};
/** Options for the camera video stream layout component. */
const VideoStreamOptions = (props: OptionsProps) => {
- const definition = props.selectedDefinition as CameraViewDefinition;
- const [showButtons, setShowButtons] = React.useState(true);
-
- function toggleButtons() {
- setShowButtons(!showButtons);
- definition.displayButtons = showButtons;
- props.updateLayout();
- }
-
- return (
-
-
-
- );
+ const definition = props.selectedDefinition as CameraViewDefinition;
+ const [showButtons, setShowButtons] = React.useState(true);
+
+ function toggleButtons() {
+ setShowButtons(!showButtons);
+ definition.displayButtons = showButtons;
+ props.updateLayout();
+ }
+
+ return (
+
+
+
+ );
};
/** Options when user selects a single tab within a panel. */
const TabOptions = (props: OptionsProps) => {
- const definition = props.selectedDefinition as TabDefinition;
- const [showRenameModal, setShowRenameModal] = React.useState(false);
- const [renameText, setRenameText] = React.useState("");
- function handleRename() {
- if (renameText.length > 0) {
- definition.label = renameText;
- props.updateLayout();
+ const definition = props.selectedDefinition as TabDefinition;
+ const [showRenameModal, setShowRenameModal] =
+ React.useState(false);
+ const [renameText, setRenameText] = React.useState("");
+ function handleRename() {
+ if (renameText.length > 0) {
+ definition.label = renameText;
+ props.updateLayout();
+ }
+ setRenameText("");
}
- setRenameText("");
- }
- return (
-