From 7372776d4406651c3f6254d053918573174c01ea Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 3 Oct 2024 15:56:13 -0400 Subject: [PATCH 01/32] Start homepage --- src/pages/home/css/index.css | 25 +++++++++++++++++++++++++ src/pages/home/html/index.html | 10 ++++++++++ src/pages/home/tsx/index.tsx | 11 +++++++++++ webpack.config.js | 5 +++-- 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/pages/home/css/index.css create mode 100644 src/pages/home/html/index.html create mode 100644 src/pages/home/tsx/index.tsx diff --git a/src/pages/home/css/index.css b/src/pages/home/css/index.css new file mode 100644 index 00000000..1ee86579 --- /dev/null +++ b/src/pages/home/css/index.css @@ -0,0 +1,25 @@ +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: rgb(245, 245, 245); + overflow: hidden; +} + +html, +body { + height: 100%; + margin: 0; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", + monospace; +} + +#root { + margin: 0; + height: 100%; +} diff --git a/src/pages/home/html/index.html b/src/pages/home/html/index.html new file mode 100644 index 00000000..ddd45c18 --- /dev/null +++ b/src/pages/home/html/index.html @@ -0,0 +1,10 @@ + + + + + Home - Stretch Web Teleop + + +
+ + diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx new file mode 100644 index 00000000..3e3a6e96 --- /dev/null +++ b/src/pages/home/tsx/index.tsx @@ -0,0 +1,11 @@ +import 'home/css/index.css'; +import React from "react"; +import { createRoot } from "react-dom/client"; + + + +const container = document.getElementById("root"); +const root = createRoot(container!); +root.render( +
Hey
+); diff --git a/webpack.config.js b/webpack.config.js index c7040818..060480b7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,7 +3,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin"); const webpack = require("webpack"); const dotenv = require("dotenv"); -const pages = ["robot", "operator"]; +const pages = ["robot", "operator", "home"]; // call dotenv and it will return an Object with a parsed key const env = dotenv.config().parsed; @@ -52,7 +52,7 @@ module.exports = (env) => { new HtmlWebpackPlugin({ inject: true, template: `./src/pages/${page}/html/index.html`, - filename: `${page}/index.html`, + filename: page == "home" ? "index.html" : `${page}/index.html`, chunks: [page], }), ), @@ -98,6 +98,7 @@ module.exports = (env) => { shared: path.resolve(__dirname, "./src/shared/"), operator: path.resolve(__dirname, "./src/pages/operator/"), robot: path.resolve(__dirname, "./src/pages/robot/"), + home: path.resolve(__dirname, "./src/pages/home/"), }, fallback: { fs: false, From 0a415696f556dc93996a046a782b2ed8837ec5e0 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 3 Oct 2024 17:37:09 -0400 Subject: [PATCH 02/32] Render homepage based on login state --- src/pages/home/tsx/index.tsx | 24 ++++++++++++---- .../login_handler/FirebaseLoginHandler.tsx | 17 +++++++++++ .../tsx/login_handler/LocalLoginHandler.tsx | 24 ++++++++++++++++ .../home/tsx/login_handler/LoginHandler.tsx | 11 ++++++++ src/pages/home/tsx/utils.tsx | 28 +++++++++++++++++++ 5 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx create mode 100644 src/pages/home/tsx/login_handler/LocalLoginHandler.tsx create mode 100644 src/pages/home/tsx/login_handler/LoginHandler.tsx create mode 100644 src/pages/home/tsx/utils.tsx diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 3e3a6e96..ab338be5 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -1,11 +1,25 @@ import 'home/css/index.css'; import React from "react"; import { createRoot } from "react-dom/client"; +import { createLoginHandler } from './utils'; +import { LoginHandler } from './login_handler/LoginHandler'; - - +let loginHandler: LoginHandler; const container = document.getElementById("root"); const root = createRoot(container!); -root.render( -
Hey
-); + +const loginHandlerReadyCallback = () => { + renderHomePage(); +}; +loginHandler = createLoginHandler(loginHandlerReadyCallback); + + +function renderHomePage() { + loginHandler.loginState() == "authenticated" + ? root.render( +
Logged In
+ ) + : root.render( +
Not Logged In
+ ); +} diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx new file mode 100644 index 00000000..06a78926 --- /dev/null +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -0,0 +1,17 @@ +import { LoginHandler } from "./LoginHandler"; + +export class FirebaseLoginHandler extends LoginHandler { + + constructor(onLoginHandlerReadyCallback: () => void, config) { + super(onLoginHandlerReadyCallback); + + // Allow the initialization process to complete before invoking the callback + setTimeout(() => { + this.onReadyCallback(); + }, 0); + } + + public loginState(): string { + return "authenticated"; + } +} diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx new file mode 100644 index 00000000..58d32242 --- /dev/null +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -0,0 +1,24 @@ +import { LoginHandler } from "./LoginHandler"; +import io, { Socket } from "socket.io-client"; + + +export class LocalLoginHandler extends LoginHandler { + private socket: Socket; + + constructor(onLoginHandlerReadyCallback: () => void) { + super(onLoginHandlerReadyCallback); + this.socket = io(); + this.socket.on("connect", () => { + console.log("Connected to local socket"); + }); + + // Allow the initialization process to complete before invoking the callback + setTimeout(() => { + this.onReadyCallback(); + }, 0); + } + + public loginState(): string { + return "authenticated"; + } +} diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx new file mode 100644 index 00000000..019fa8da --- /dev/null +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -0,0 +1,11 @@ + + +export abstract class LoginHandler { + public onReadyCallback: () => void; + + constructor(onLoginHandlerReadyCallback: () => void) { + this.onReadyCallback = onLoginHandlerReadyCallback; + } + + public abstract loginState(): string; +} diff --git a/src/pages/home/tsx/utils.tsx b/src/pages/home/tsx/utils.tsx new file mode 100644 index 00000000..8b935e9c --- /dev/null +++ b/src/pages/home/tsx/utils.tsx @@ -0,0 +1,28 @@ +import { FirebaseOptions } from "firebase/app"; +import { FirebaseLoginHandler } from "./login_handler/FirebaseLoginHandler"; +import { LocalLoginHandler } from "./login_handler/LocalLoginHandler"; + + +/** + * Creates a login handler based on the `storage` property in the process + * environment. + * + * @returns the login handler + */ +export function createLoginHandler(loginHandlerReadyCallback: () => 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 FirebaseLoginHandler(loginHandlerReadyCallback, config); + default: + return new LocalLoginHandler(loginHandlerReadyCallback); + } +} From bbbbf8a45a5b9462f5baa8f3affe6c6ff7eb0046 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 3 Oct 2024 18:17:29 -0400 Subject: [PATCH 03/32] WIP: Robot selector --- package.json | 4 +- server.js | 9 +++ src/pages/home/css/CallRobotSelector.css | 4 ++ src/pages/home/css/Changelog.css | 4 ++ src/pages/home/css/SideBySideView.css | 4 ++ src/pages/home/css/index.css | 1 - .../home/tsx/components/CallRobotSelector.tsx | 71 +++++++++++++++++++ src/pages/home/tsx/components/Changelog.tsx | 66 +++++++++++++++++ .../home/tsx/components/SideBySideView.tsx | 41 +++++++++++ src/pages/home/tsx/index.tsx | 3 +- .../login_handler/FirebaseLoginHandler.tsx | 6 ++ .../tsx/login_handler/LocalLoginHandler.tsx | 8 +++ .../home/tsx/login_handler/LoginHandler.tsx | 2 + 13 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 src/pages/home/css/CallRobotSelector.css create mode 100644 src/pages/home/css/Changelog.css create mode 100644 src/pages/home/css/SideBySideView.css create mode 100644 src/pages/home/tsx/components/CallRobotSelector.tsx create mode 100644 src/pages/home/tsx/components/Changelog.tsx create mode 100644 src/pages/home/tsx/components/SideBySideView.tsx diff --git a/package.json b/package.json index 610fc50f..952eb203 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "dependencies": { "@emotion/react": "^11.13.0", "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^5.16.6", - "@mui/material": "^5.16.6", + "@mui/icons-material": "^6.1.2", + "@mui/material": "^6.1.2", "@types/createjs": "^0.0.29", "@types/react": "^18.0.34", "@types/react-dom": "^18.0.11", diff --git a/server.js b/server.js index 9fccbe47..edd46e54 100644 --- a/server.js +++ b/server.js @@ -74,6 +74,15 @@ io.on("connection", function (socket) { } }); + socket.on("list_rooms", (callback) => { + callback([{ + "roomid": "robot", + "protocol": undefined, // TODO(binit): ensure robot/operator protocol match + "is_online": true, // TODO + "is_occupied": true, // TODO + }]); + }); + 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")) { diff --git a/src/pages/home/css/CallRobotSelector.css b/src/pages/home/css/CallRobotSelector.css new file mode 100644 index 00000000..339e1d4c --- /dev/null +++ b/src/pages/home/css/CallRobotSelector.css @@ -0,0 +1,4 @@ + +.rs-container { + overflow: auto; +} diff --git a/src/pages/home/css/Changelog.css b/src/pages/home/css/Changelog.css new file mode 100644 index 00000000..75edf24e --- /dev/null +++ b/src/pages/home/css/Changelog.css @@ -0,0 +1,4 @@ + +.cv-container { + overflow: auto; +} diff --git a/src/pages/home/css/SideBySideView.css b/src/pages/home/css/SideBySideView.css new file mode 100644 index 00000000..641879cd --- /dev/null +++ b/src/pages/home/css/SideBySideView.css @@ -0,0 +1,4 @@ + +.sbs-container { + padding: 20px; +} diff --git a/src/pages/home/css/index.css b/src/pages/home/css/index.css index 1ee86579..0127118d 100644 --- a/src/pages/home/css/index.css +++ b/src/pages/home/css/index.css @@ -5,7 +5,6 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: rgb(245, 245, 245); - overflow: hidden; } html, diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx new file mode 100644 index 00000000..8059e798 --- /dev/null +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -0,0 +1,71 @@ +import "home/css/CallRobotSelector.css"; +import React, { useEffect, useState } from "react"; +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid2'; +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import { styled } from '@mui/material/styles'; +import Paper from '@mui/material/Paper'; + + +const Item = styled(Paper)(({ theme }) => ({ + backgroundColor: '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + textAlign: 'center', + color: theme.palette.text.secondary, + ...theme.applyStyles('dark', { + backgroundColor: '#1A2027', + }), +})); + +const CallRobotItem = () => { + + return ( + + + + Word of the Day + + + benevolent + + adjective + + well meaning and kindly. +
+ {'"a benevolent smile"'} +
+ + + +
+
+ ); +}; + + +export const CallRobotSelector = (props: { + style?: React.CSSProperties; +}) => { + + return ( + +

Robots:

+ + + + + + + + + + + +
+ ); +}; \ No newline at end of file diff --git a/src/pages/home/tsx/components/Changelog.tsx b/src/pages/home/tsx/components/Changelog.tsx new file mode 100644 index 00000000..5ee77d60 --- /dev/null +++ b/src/pages/home/tsx/components/Changelog.tsx @@ -0,0 +1,66 @@ +import "home/css/Changelog.css"; +import React, { useEffect, useState } from "react"; +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid2'; +import { styled } from '@mui/material/styles'; +import Paper from '@mui/material/Paper'; + +const LogItem = styled(Paper)(({ theme }) => ({ + backgroundColor: '#fff', + ...theme.typography.body2, + padding: theme.spacing(1), + color: theme.palette.text.secondary, + ...theme.applyStyles('dark', { + backgroundColor: '#1A2027', + }), +})); + + +export const Changelog = (props: { + style?: React.CSSProperties; +}) => { + + return ( + +

What's new?

+ + + + Login Page - Oct 9th, 2024 +

The new homepage shows the robots you can call.

+
+
+ + + Homing Button - Sept 21st, 2024 +

A button appears if your robot needs to be homed.

+
+
+ + + Blah blah - May 18st, 2024 +

Lorem ipsum dolor et.

+
+
+ + + Blah blah - May 18st, 2024 +

Lorem ipsum dolor et.

+
+
+ + + Blah blah - May 18st, 2024 +

Lorem ipsum dolor et.

+
+
+ + + Blah blah - May 18st, 2024 +

Lorem ipsum dolor et.

+
+
+
+
+ ); +}; diff --git a/src/pages/home/tsx/components/SideBySideView.tsx b/src/pages/home/tsx/components/SideBySideView.tsx new file mode 100644 index 00000000..d2254f32 --- /dev/null +++ b/src/pages/home/tsx/components/SideBySideView.tsx @@ -0,0 +1,41 @@ +import "home/css/SideBySideView.css"; +import React, { useEffect, useState } from "react"; +import { isTablet, isBrowser } from "react-device-detect"; +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid2'; +import { Changelog } from "./Changelog"; +import { CallRobotSelector } from "./CallRobotSelector"; + + +export const SideBySideView = (props) => { + + return isTablet || isBrowser ? ( + + + +

Stretch Web Teleop

+
+ + + + + + +
+
+ ) : ( + + + +

Stretch Web Teleop

+
+ + + + + + +
+
+ ); +}; diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index ab338be5..5aa536a0 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -3,6 +3,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; import { createLoginHandler } from './utils'; import { LoginHandler } from './login_handler/LoginHandler'; +import { SideBySideView } from './components/SideBySideView'; let loginHandler: LoginHandler; const container = document.getElementById("root"); @@ -17,7 +18,7 @@ loginHandler = createLoginHandler(loginHandlerReadyCallback); function renderHomePage() { loginHandler.loginState() == "authenticated" ? root.render( -
Logged In
+ ) : root.render(
Not Logged In
diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index 06a78926..29c3817b 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -14,4 +14,10 @@ export class FirebaseLoginHandler extends LoginHandler { public loginState(): string { return "authenticated"; } + + public listRooms(): Promise { + return new Promise((resolve) => { + // TODO(binit) + }); + } } diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 58d32242..9a689714 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -21,4 +21,12 @@ export class LocalLoginHandler extends LoginHandler { public loginState(): string { return "authenticated"; } + + public listRooms(): Promise { + return new Promise((resolve) => { + this.socket.emit("list_rooms", (rooms) => { + resolve(rooms); + }); + }); + } } diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index 019fa8da..0bd12c34 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -8,4 +8,6 @@ export abstract class LoginHandler { } public abstract loginState(): string; + + public abstract listRooms(): Promise; } From 3cdfee7fcd68a3e4ef94f3a155d917846c09ac11 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Mon, 21 Oct 2024 17:15:23 -0400 Subject: [PATCH 04/32] Fill out caller card in robot selector --- .../home/tsx/components/CallRobotSelector.tsx | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index 8059e798..54f2121b 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -3,46 +3,69 @@ import React, { useEffect, useState } from "react"; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid2'; import Card from '@mui/material/Card'; -import CardActions from '@mui/material/CardActions'; +import CardActionArea from "@mui/material/CardActionArea"; import CardContent from '@mui/material/CardContent'; -import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; -import { styled } from '@mui/material/styles'; -import Paper from '@mui/material/Paper'; +import CircleIcon from "@mui/icons-material/Circle"; +import { green, red } from '@mui/material/colors'; -const Item = styled(Paper)(({ theme }) => ({ - backgroundColor: '#fff', - ...theme.typography.body2, - padding: theme.spacing(1), - textAlign: 'center', - color: theme.palette.text.secondary, - ...theme.applyStyles('dark', { - backgroundColor: '#1A2027', - }), -})); - -const CallRobotItem = () => { +const CallRobotItem = (props: { + name: String; + isOnline: Boolean; +}) => { return ( - - - Word of the Day - - - benevolent - - adjective - - well meaning and kindly. -
- {'"a benevolent smile"'} -
- - - -
+ {props.isOnline ? ( + console.log("CardActionArea clicked")}> + + + {props.name} + + + Online + + + + ) : ( + + + {props.name} + + + Offline + + + )}
); }; @@ -57,13 +80,10 @@ export const CallRobotSelector = (props: {

Robots:

- - - - + - + From ace693778d120e12f2fce9d169552f7672677ade Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Mon, 21 Oct 2024 18:16:59 -0400 Subject: [PATCH 05/32] Show occupied robots in selector --- server.js | 3 +- .../home/tsx/components/CallRobotSelector.tsx | 153 +++++++++++------- 2 files changed, 99 insertions(+), 57 deletions(-) diff --git a/server.js b/server.js index edd46e54..f1fca5e2 100644 --- a/server.js +++ b/server.js @@ -78,8 +78,7 @@ io.on("connection", function (socket) { callback([{ "roomid": "robot", "protocol": undefined, // TODO(binit): ensure robot/operator protocol match - "is_online": true, // TODO - "is_occupied": true, // TODO + "status": "online" // ["online", "offline", "occupied"] TODO(binit): don't hardcode }]); }); diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index 54f2121b..80ca0980 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -3,69 +3,106 @@ import React, { useEffect, useState } from "react"; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid2'; import Card from '@mui/material/Card'; -import CardActionArea from "@mui/material/CardActionArea"; import CardContent from '@mui/material/CardContent'; +import CardActions from '@mui/material/CardActions'; +import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import CircleIcon from "@mui/icons-material/Circle"; -import { green, red } from '@mui/material/colors'; +import { green, red, yellow, grey } from '@mui/material/colors'; +function get_indicator_text(status_str) { + switch (status_str) { + case "online": + return "Online" + case "offline": + return "Offline" + case "occupied": + return "Occupied" + default: + return "Unknown" + } +} + + +function get_indicator(status_str) { + let statusui; + switch (status_str) { + case "online": + statusui = { + "color_name": "green", + "color": green, + }; + break; + case "offline": + statusui = { + "color_name": "red", + "color": red, + }; + break; + case "occupied": + statusui = { + "color_name": "yellow", + "color": yellow, + }; + break; + default: + statusui = { + "color_name": "grey", + "color": grey, + }; + } + let indicator_css = { + fontSize: 12, + color: statusui['color']["A400"], + animation: `glowing_${statusui['color_name']} 3s linear infinite` + }; + indicator_css[`@keyframes glowing_${statusui['color_name']}`] = { + "0%": { + color: statusui['color']["A400"] + }, + "50%": { + color: statusui['color']["A200"] + }, + "100%": { + color: statusui['color']["A400"] + } + }; + return +} + + + +function get_action(status_str) { + switch (status_str) { + case "online": + return + case "offline": + return + case "occupied": + return + default: + return + } +} const CallRobotItem = (props: { name: String; - isOnline: Boolean; + status: String; }) => { return ( - {props.isOnline ? ( - console.log("CardActionArea clicked")}> - - - {props.name} - - - Online - - - - ) : ( - - - {props.name} - - - Offline - - - )} + + + {props.name} + + + {get_indicator(props.status)} {get_indicator_text(props.status)} + + + + {get_action(props.status)} + ); }; @@ -80,12 +117,18 @@ export const CallRobotSelector = (props: {

Robots:

- + - + + + + + + + ); -}; \ No newline at end of file +}; From e510be2e0b2902570b1740871969efd31cb84233 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 15:54:07 -0400 Subject: [PATCH 06/32] Try a no-paper design for Changelog elements --- src/pages/home/tsx/components/Changelog.tsx | 35 ++++++++------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/pages/home/tsx/components/Changelog.tsx b/src/pages/home/tsx/components/Changelog.tsx index 5ee77d60..df545a85 100644 --- a/src/pages/home/tsx/components/Changelog.tsx +++ b/src/pages/home/tsx/components/Changelog.tsx @@ -3,16 +3,13 @@ import React, { useEffect, useState } from "react"; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid2'; import { styled } from '@mui/material/styles'; -import Paper from '@mui/material/Paper'; -const LogItem = styled(Paper)(({ theme }) => ({ - backgroundColor: '#fff', + +const LogItem = styled(Box)(({ theme }) => ({ ...theme.typography.body2, - padding: theme.spacing(1), + paddingLeft: theme.spacing(1), color: theme.palette.text.secondary, - ...theme.applyStyles('dark', { - backgroundColor: '#1A2027', - }), + fontSize: 17, })); @@ -23,41 +20,35 @@ export const Changelog = (props: { return (

What's new?

- + Login Page - Oct 9th, 2024 -

The new homepage shows the robots you can call.

+

The new homepage shows the robots you can call, or as "unavailable" if the robot is powered off or occupied by another operator. There's also a "What's New" section with details about new features being added to the web interface. By default, you are logged in when running the interface locally, but there's a login screen that can be accessed by logging out. In the future, this login screen will enable you to call your Stretch over the internet.

Homing Button - Sept 21st, 2024 -

A button appears if your robot needs to be homed.

-
-
- - - Blah blah - May 18st, 2024 -

Lorem ipsum dolor et.

+

The operator interface now shows an banner if your robot needs to be homed. Since some of Stretch's encoders are relative, there's a homing sequence to find zero for those joints when Stretch wakes up. Previously, developers had to use a terminal to trigger Stretch's homing sequence, but now you can do it through the web interface.

- Blah blah - May 18st, 2024 -

Lorem ipsum dolor et.

+ Lorem ipsum dolor et. - May 18st, 2024 +

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. Feugiat fringilla tristique varius feugiat quis cras magnis efficitur. Aptent curabitur mattis dui congue porta cubilia. Lorem scelerisque convallis tempor himenaeos donec inceptos ultricies dis. Efficitur feugiat senectus nullam semper conubia risus mi volutpat.

- Blah blah - May 18st, 2024 -

Lorem ipsum dolor et.

+ Lorem ipsum dolor et. - Mar 2nd, 2024 +

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. Feugiat fringilla tristique varius feugiat quis cras magnis efficitur. Aptent curabitur mattis dui congue porta cubilia. Lorem scelerisque convallis tempor himenaeos donec inceptos ultricies dis. Efficitur feugiat senectus nullam semper conubia risus mi volutpat.

- Blah blah - May 18st, 2024 -

Lorem ipsum dolor et.

+ Lorem ipsum dolor et. - Jan 31st, 2024 +

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. Feugiat fringilla tristique varius feugiat quis cras magnis efficitur. Aptent curabitur mattis dui congue porta cubilia. Lorem scelerisque convallis tempor himenaeos donec inceptos ultricies dis. Efficitur feugiat senectus nullam semper conubia risus mi volutpat.

From 65c77d80405fff7f10ddba534540176870a6d755 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 16:16:04 -0400 Subject: [PATCH 07/32] Use AppBar for homepage title + logout button --- .../home/tsx/components/SideBySideView.tsx | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/pages/home/tsx/components/SideBySideView.tsx b/src/pages/home/tsx/components/SideBySideView.tsx index d2254f32..a4a4d2ed 100644 --- a/src/pages/home/tsx/components/SideBySideView.tsx +++ b/src/pages/home/tsx/components/SideBySideView.tsx @@ -3,6 +3,10 @@ import React, { useEffect, useState } from "react"; import { isTablet, isBrowser } from "react-device-detect"; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid2'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; import { Changelog } from "./Changelog"; import { CallRobotSelector } from "./CallRobotSelector"; @@ -10,11 +14,16 @@ import { CallRobotSelector } from "./CallRobotSelector"; export const SideBySideView = (props) => { return isTablet || isBrowser ? ( - - - -

Stretch Web Teleop

-
+ + + + + Stretch Web Teleop + + + + + @@ -24,11 +33,16 @@ export const SideBySideView = (props) => { ) : ( - - - -

Stretch Web Teleop

-
+ + + + + Stretch Web Teleop + + + + + From 57642a0165a7a341d020d66ff92adae8b6460a66 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 16:30:07 -0400 Subject: [PATCH 08/32] Trigger logout with logout button --- src/pages/home/tsx/components/SideBySideView.tsx | 5 +++-- src/pages/home/tsx/index.tsx | 2 +- src/pages/home/tsx/login_handler/LocalLoginHandler.tsx | 10 +++++++++- src/pages/home/tsx/login_handler/LoginHandler.tsx | 2 ++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pages/home/tsx/components/SideBySideView.tsx b/src/pages/home/tsx/components/SideBySideView.tsx index a4a4d2ed..df71b3ea 100644 --- a/src/pages/home/tsx/components/SideBySideView.tsx +++ b/src/pages/home/tsx/components/SideBySideView.tsx @@ -9,6 +9,7 @@ import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import { Changelog } from "./Changelog"; import { CallRobotSelector } from "./CallRobotSelector"; +import { loginHandler } from "../index"; export const SideBySideView = (props) => { @@ -20,7 +21,7 @@ export const SideBySideView = (props) => { Stretch Web Teleop - + @@ -39,7 +40,7 @@ export const SideBySideView = (props) => { Stretch Web Teleop - + diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 5aa536a0..7e9af75c 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -5,7 +5,7 @@ import { createLoginHandler } from './utils'; import { LoginHandler } from './login_handler/LoginHandler'; import { SideBySideView } from './components/SideBySideView'; -let loginHandler: LoginHandler; +export let loginHandler: LoginHandler; const container = document.getElementById("root"); const root = createRoot(container!); diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 9a689714..22819b07 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -4,14 +4,17 @@ import io, { Socket } from "socket.io-client"; export class LocalLoginHandler extends LoginHandler { private socket: Socket; + private _loginState: string; constructor(onLoginHandlerReadyCallback: () => void) { super(onLoginHandlerReadyCallback); + this._loginState = "authenticated"; this.socket = io(); this.socket.on("connect", () => { console.log("Connected to local socket"); }); + this.logout = this.logout.bind(this); // Allow the initialization process to complete before invoking the callback setTimeout(() => { this.onReadyCallback(); @@ -19,7 +22,7 @@ export class LocalLoginHandler extends LoginHandler { } public loginState(): string { - return "authenticated"; + return this._loginState; } public listRooms(): Promise { @@ -29,4 +32,9 @@ export class LocalLoginHandler extends LoginHandler { }); }); } + + public logout() { + this._loginState = "not_authenticated"; + this.onReadyCallback(); + } } diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index 0bd12c34..b3ee412f 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -10,4 +10,6 @@ export abstract class LoginHandler { public abstract loginState(): string; public abstract listRooms(): Promise; + + public abstract logout(); } From 93d56bb13ea28bf6317879e9cddac67d67186f87 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 18:20:19 -0400 Subject: [PATCH 09/32] Add login view --- src/pages/home/css/LoginView.css | 4 + src/pages/home/tsx/components/LoginView.tsx | 178 ++++++++++++++++++ src/pages/home/tsx/index.tsx | 3 +- .../tsx/login_handler/LocalLoginHandler.tsx | 2 +- 4 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/pages/home/css/LoginView.css create mode 100644 src/pages/home/tsx/components/LoginView.tsx diff --git a/src/pages/home/css/LoginView.css b/src/pages/home/css/LoginView.css new file mode 100644 index 00000000..34bc3344 --- /dev/null +++ b/src/pages/home/css/LoginView.css @@ -0,0 +1,4 @@ + +.lv-container { + padding: 20px; +} diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx new file mode 100644 index 00000000..b2acd79e --- /dev/null +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -0,0 +1,178 @@ +// This component comes from the template at: +// https://github.com/mui/material-ui/tree/v6.1.5/docs/data/material/getting-started/templates/sign-in + +import "home/css/LoginView.css"; +import React, { useEffect, useState } from "react"; +import { isTablet, isBrowser } from "react-device-detect"; +import Box from '@mui/material/Box'; +import MuiCard from '@mui/material/Card'; +import Typography from '@mui/material/Typography'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import TextField from '@mui/material/TextField'; +import Link from '@mui/material/Link'; +import Checkbox from '@mui/material/Checkbox'; +import Button from '@mui/material/Button'; +import { styled } from '@mui/material/styles'; + +const Card = styled(MuiCard)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignSelf: 'center', + width: '100%', + padding: theme.spacing(4), + gap: theme.spacing(2), + margin: 'auto', + [theme.breakpoints.up('sm')]: { + maxWidth: '450px', + }, + boxShadow: + 'hsla(220, 30%, 5%, 0.05) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.05) 0px 15px 35px -5px', + ...theme.applyStyles('dark', { + boxShadow: + 'hsla(220, 30%, 5%, 0.5) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.08) 0px 15px 35px -5px', + }), +})); + + +export const LoginView = (props) => { + const [emailError, setEmailError] = useState(false); + const [emailErrorMessage, setEmailErrorMessage] = useState(''); + const [passwordError, setPasswordError] = useState(false); + const [passwordErrorMessage, setPasswordErrorMessage] = useState(''); + const [open, setOpen] = useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (emailError || passwordError) { + return; + } + + const data = new FormData(event.currentTarget); + console.log({ + email: data.get('email'), + password: data.get('password'), + remember: data.get('remember') ? true : false, + }); + }; + + const validateInputs = () => { + const email = document.getElementById('email') as HTMLInputElement; + const password = document.getElementById('password') as HTMLInputElement; + + let isValid = true; + + if (!email.value || !/\S+@\S+\.\S+/.test(email.value)) { + setEmailError(true); + setEmailErrorMessage('Please enter a valid email address.'); + isValid = false; + } else { + setEmailError(false); + setEmailErrorMessage(''); + } + + if (!password.value || password.value.length < 6) { + setPasswordError(true); + setPasswordErrorMessage('Password must be at least 6 characters long.'); + isValid = false; + } else { + setPasswordError(false); + setPasswordErrorMessage(''); + } + + return isValid; + }; + + return isTablet || isBrowser ? ( + + + + Sign in + + + + Email + + + + + Password + + Forgot your password? + + + + + } + label="Remember me" /> + {/* */} + + + + + ) : ( +

Not implemented

+ ); +}; diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 7e9af75c..3395aed4 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -4,6 +4,7 @@ import { createRoot } from "react-dom/client"; import { createLoginHandler } from './utils'; import { LoginHandler } from './login_handler/LoginHandler'; import { SideBySideView } from './components/SideBySideView'; +import { LoginView } from './components/LoginView'; export let loginHandler: LoginHandler; const container = document.getElementById("root"); @@ -21,6 +22,6 @@ function renderHomePage() { ) : root.render( -
Not Logged In
+ ); } diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 22819b07..f36c0e48 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -8,7 +8,7 @@ export class LocalLoginHandler extends LoginHandler { constructor(onLoginHandlerReadyCallback: () => void) { super(onLoginHandlerReadyCallback); - this._loginState = "authenticated"; + this._loginState = "not_authenticated"; this.socket = io(); this.socket.on("connect", () => { console.log("Connected to local socket"); From 19681ca43c761756ce988296d686b24496b3d66a Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 19:13:28 -0400 Subject: [PATCH 10/32] Add forgot password component --- .../home/tsx/components/ForgotPassword.tsx | 57 +++++++++++++++++++ src/pages/home/tsx/components/LoginView.tsx | 22 ++++++- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/pages/home/tsx/components/ForgotPassword.tsx diff --git a/src/pages/home/tsx/components/ForgotPassword.tsx b/src/pages/home/tsx/components/ForgotPassword.tsx new file mode 100644 index 00000000..b78f5ef7 --- /dev/null +++ b/src/pages/home/tsx/components/ForgotPassword.tsx @@ -0,0 +1,57 @@ +// This component comes from the template at: +// https://github.com/mui/material-ui/tree/v6.1.5/docs/data/material/getting-started/templates/sign-in + +import React, { useEffect, useState } from "react"; +import { isTablet, isBrowser } from "react-device-detect"; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogActions from '@mui/material/DialogActions'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import Button from '@mui/material/Button'; + + +export const ForgotPassword = (props: { + open: boolean; + handleClose: () => void; + handleExecute: (email) => void; +}) => { + + return isTablet || isBrowser ? ( + ) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + props.handleExecute(data.get('email')); + props.handleClose(); + event.stopPropagation(); + }}}> + Reset password + + + Enter your account's email address, and we'll send you a link to reset your password. + + + + + + + + + ) : ( +

Not implemented

+ ); +}; diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index b2acd79e..60606839 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -14,7 +14,10 @@ import TextField from '@mui/material/TextField'; import Link from '@mui/material/Link'; import Checkbox from '@mui/material/Checkbox'; import Button from '@mui/material/Button'; +import Snackbar from '@mui/material/Snackbar'; import { styled } from '@mui/material/styles'; +import { ForgotPassword } from "./ForgotPassword"; + const Card = styled(MuiCard)(({ theme }) => ({ display: 'flex', @@ -42,15 +45,25 @@ export const LoginView = (props) => { const [passwordError, setPasswordError] = useState(false); const [passwordErrorMessage, setPasswordErrorMessage] = useState(''); const [open, setOpen] = useState(false); + const [openToast, setOpenToast] = useState(false); const handleClickOpen = () => { setOpen(true); }; + const handleForgotPassword = (email: string) => { + console.log('handleForgotPassword', email); + setOpenToast(true); + }; + const handleClose = () => { setOpen(false); }; + const handleToastClose = () => { + setOpenToast(false); + }; + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); if (emailError || passwordError) { @@ -152,7 +165,6 @@ export const LoginView = (props) => { type="password" id="password" autoComplete="current-password" - autoFocus required fullWidth variant="outlined" @@ -161,7 +173,7 @@ export const LoginView = (props) => { } label="Remember me" /> - {/* */} +
+
) : (

Not implemented

From 6190a5db26a2ab686ace1dfc5402087739067cea Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 19:22:23 -0400 Subject: [PATCH 11/32] Wire login details to login handler --- src/pages/home/tsx/components/ForgotPassword.tsx | 4 ++-- src/pages/home/tsx/components/LoginView.tsx | 10 ++++++---- src/pages/home/tsx/login_handler/LocalLoginHandler.tsx | 5 +++++ src/pages/home/tsx/login_handler/LoginHandler.tsx | 2 ++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/pages/home/tsx/components/ForgotPassword.tsx b/src/pages/home/tsx/components/ForgotPassword.tsx index b78f5ef7..248e07dc 100644 --- a/src/pages/home/tsx/components/ForgotPassword.tsx +++ b/src/pages/home/tsx/components/ForgotPassword.tsx @@ -15,7 +15,7 @@ import Button from '@mui/material/Button'; export const ForgotPassword = (props: { open: boolean; handleClose: () => void; - handleExecute: (email) => void; + handleExecute: (email: string) => void; }) => { return isTablet || isBrowser ? ( @@ -27,7 +27,7 @@ export const ForgotPassword = (props: { onSubmit: (event: React.FormEvent) => { event.preventDefault(); const data = new FormData(event.currentTarget); - props.handleExecute(data.get('email')); + props.handleExecute(data.get('email') as string); props.handleClose(); event.stopPropagation(); }}}> diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index 60606839..3a51cd4d 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -17,6 +17,7 @@ import Button from '@mui/material/Button'; import Snackbar from '@mui/material/Snackbar'; import { styled } from '@mui/material/styles'; import { ForgotPassword } from "./ForgotPassword"; +import { loginHandler } from "../index"; const Card = styled(MuiCard)(({ theme }) => ({ @@ -71,11 +72,12 @@ export const LoginView = (props) => { } const data = new FormData(event.currentTarget); - console.log({ - email: data.get('email'), - password: data.get('password'), + let l = { + email: data.get('email') as string, + password: data.get('password') as string, remember: data.get('remember') ? true : false, - }); + }; + loginHandler.login(l['email'], l['password'], l['remember']) }; const validateInputs = () => { diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index f36c0e48..46531def 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -37,4 +37,9 @@ export class LocalLoginHandler extends LoginHandler { this._loginState = "not_authenticated"; this.onReadyCallback(); } + + public login(username: string, password: string, remember_me: boolean) { + this._loginState = "authenticated"; + this.onReadyCallback(); + } } diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index b3ee412f..3d3db332 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -12,4 +12,6 @@ export abstract class LoginHandler { public abstract listRooms(): Promise; public abstract logout(); + + public abstract login(username: string, password: string, remember_me: boolean); } From 44ad934e766d927368b78d80cb28ab598173d252 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 19:25:34 -0400 Subject: [PATCH 12/32] Wire forgot password to login handler --- src/pages/home/tsx/components/LoginView.tsx | 2 +- src/pages/home/tsx/login_handler/LocalLoginHandler.tsx | 4 ++++ src/pages/home/tsx/login_handler/LoginHandler.tsx | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index 3a51cd4d..9743b1c4 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -53,7 +53,7 @@ export const LoginView = (props) => { }; const handleForgotPassword = (email: string) => { - console.log('handleForgotPassword', email); + loginHandler.forgot_password(email); setOpenToast(true); }; diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 46531def..3e811941 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -42,4 +42,8 @@ export class LocalLoginHandler extends LoginHandler { this._loginState = "authenticated"; this.onReadyCallback(); } + + public forgot_password(username: string) { + // saddness :( + } } diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index 3d3db332..9f955aff 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -14,4 +14,6 @@ export abstract class LoginHandler { public abstract logout(); public abstract login(username: string, password: string, remember_me: boolean); + + public abstract forgot_password(username: string); } From 538e7c9285693f35cbe818dc9bf63f84a9b0550e Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 19:28:29 -0400 Subject: [PATCH 13/32] Local login starts authenticated by default --- src/pages/home/tsx/login_handler/LocalLoginHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 3e811941..e065b7d0 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -8,7 +8,7 @@ export class LocalLoginHandler extends LoginHandler { constructor(onLoginHandlerReadyCallback: () => void) { super(onLoginHandlerReadyCallback); - this._loginState = "not_authenticated"; + this._loginState = "authenticated"; this.socket = io(); this.socket.on("connect", () => { console.log("Connected to local socket"); From c30e96ef8098c37f7ba67ac58db2a55e7735bdd6 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 19:49:42 -0400 Subject: [PATCH 14/32] Wire callable robots through login handler --- server.js | 2 +- .../home/tsx/components/CallRobotSelector.tsx | 29 +++++++++++-------- .../tsx/login_handler/LocalLoginHandler.tsx | 8 ++--- .../home/tsx/login_handler/LoginHandler.tsx | 2 +- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/server.js b/server.js index f1fca5e2..e33a5339 100644 --- a/server.js +++ b/server.js @@ -76,7 +76,7 @@ io.on("connection", function (socket) { socket.on("list_rooms", (callback) => { callback([{ - "roomid": "robot", + "roomid": process.env.HELLO_FLEET_ID, "protocol": undefined, // TODO(binit): ensure robot/operator protocol match "status": "online" // ["online", "offline", "occupied"] TODO(binit): don't hardcode }]); diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index 80ca0980..84f48697 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -9,6 +9,8 @@ import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import CircleIcon from "@mui/icons-material/Circle"; import { green, red, yellow, grey } from '@mui/material/colors'; +import { loginHandler } from "../index"; + function get_indicator_text(status_str) { switch (status_str) { @@ -111,23 +113,26 @@ const CallRobotItem = (props: { export const CallRobotSelector = (props: { style?: React.CSSProperties; }) => { + const [callableRobots, setCallableRobots] = useState([]); + + const handleCRResult = (result) => { + setCallableRobots(result); + }; + loginHandler.listRooms(handleCRResult); return (

Robots:

- - - - - - - - - - - - + { + callableRobots.map((robot, idx) => { + return ( + + + + ) + }) + }
); diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index e065b7d0..6bde8a63 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -25,12 +25,8 @@ export class LocalLoginHandler extends LoginHandler { return this._loginState; } - public listRooms(): Promise { - return new Promise((resolve) => { - this.socket.emit("list_rooms", (rooms) => { - resolve(rooms); - }); - }); + public listRooms(resultCallback) { + this.socket.emit("list_rooms", resultCallback); } public logout() { diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index 9f955aff..88faadd2 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -9,7 +9,7 @@ export abstract class LoginHandler { public abstract loginState(): string; - public abstract listRooms(): Promise; + public abstract listRooms(resultCallback); public abstract logout(); From dc53068c63d814e34886ab6f83f44b9d7d71a84c Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Tue, 22 Oct 2024 22:12:42 -0400 Subject: [PATCH 15/32] Local server reports if robot is occupied --- server.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index e33a5339..f1a531fe 100644 --- a/server.js +++ b/server.js @@ -75,10 +75,15 @@ io.on("connection", function (socket) { }); socket.on("list_rooms", (callback) => { + let s = "online"; + if (!(io.sockets.adapter.rooms.get(room) || io.sockets.adapter.rooms.get(room).size < 2)) { + s = "occupied" + } + callback([{ "roomid": process.env.HELLO_FLEET_ID, "protocol": undefined, // TODO(binit): ensure robot/operator protocol match - "status": "online" // ["online", "offline", "occupied"] TODO(binit): don't hardcode + "status": s // ["online", "offline", "occupied"] }]); }); From bafc17a0525dc073fe17d6cc5be191b1c38edc65 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 11:31:11 -0400 Subject: [PATCH 16/32] Fix occupied logic --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index f1a531fe..77a0f9d6 100644 --- a/server.js +++ b/server.js @@ -76,7 +76,7 @@ io.on("connection", function (socket) { socket.on("list_rooms", (callback) => { let s = "online"; - if (!(io.sockets.adapter.rooms.get(room) || io.sockets.adapter.rooms.get(room).size < 2)) { + if (io.sockets.adapter.rooms.get("robot") && io.sockets.adapter.rooms.get("robot").size >= 2) { s = "occupied" } From 97e88cdbbb324446ca72f1d6b4ea1bec98fa0b17 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 11:52:15 -0400 Subject: [PATCH 17/32] Change title based on login state --- src/pages/home/tsx/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 3395aed4..1d27c5cb 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -17,6 +17,10 @@ loginHandler = createLoginHandler(loginHandlerReadyCallback); function renderHomePage() { + loginHandler.loginState() == "authenticated" + ? document.title = "Home - Stretch Web Teleop" + : document.title = "Login - Stretch Web Teleop"; + loginHandler.loginState() == "authenticated" ? root.render( From 8acd5b3f3e2cb5d34cba1e4d3060bd01ed3a4c69 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 12:12:30 -0400 Subject: [PATCH 18/32] Add page links to robot call buttons --- src/pages/home/tsx/components/CallRobotSelector.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index 84f48697..e75f11d3 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -74,16 +74,16 @@ function get_indicator(status_str) { -function get_action(status_str) { +function get_action(status_str, robot_name) { switch (status_str) { case "online": - return + return case "offline": - return + return case "occupied": - return + return default: - return + return } } @@ -103,7 +103,7 @@ const CallRobotItem = (props: { - {get_action(props.status)} + {get_action(props.status, props.name)} ); From 1eb74d264fb47d023bcabb4c45e4ac24d0f3604e Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 15:25:07 -0400 Subject: [PATCH 19/32] WIP firebase login handler --- src/pages/home/tsx/components/LoginView.tsx | 25 +++++-- .../login_handler/FirebaseLoginHandler.tsx | 66 ++++++++++++++++--- .../tsx/login_handler/LocalLoginHandler.tsx | 7 +- .../home/tsx/login_handler/LoginHandler.tsx | 2 +- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index 9743b1c4..345ed324 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -47,14 +47,22 @@ export const LoginView = (props) => { const [passwordErrorMessage, setPasswordErrorMessage] = useState(''); const [open, setOpen] = useState(false); const [openToast, setOpenToast] = useState(false); + const [openFailureToast, setOpenFailureToast] = useState(false); + const [failureToastMessage, setfailureToastMessage] = useState(''); const handleClickOpen = () => { setOpen(true); }; const handleForgotPassword = (email: string) => { - loginHandler.forgot_password(email); - setOpenToast(true); + loginHandler.forgot_password(email) + .then(() => { + setOpenToast(true); + }) + .catch((error) => { + setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); + setOpenFailureToast(true); + }); }; const handleClose = () => { @@ -173,7 +181,7 @@ export const LoginView = (props) => { color={passwordError ? 'error' : 'primary'} /> } + control={} label="Remember me" /> + @@ -32,6 +43,15 @@ export const SideBySideView = (props) => {
+
) : ( @@ -40,7 +60,7 @@ export const SideBySideView = (props) => { Stretch Web Teleop - + @@ -51,6 +71,15 @@ export const SideBySideView = (props) => {
+
); }; diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index 6de2107a..3bbd80f4 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -27,17 +27,14 @@ export class FirebaseLoginHandler extends LoginHandler { // TODO(binit) } - public logout() { + public logout(): Promise { // Tutorial here: // https://firebase.google.com/docs/auth/web/password-auth#next_steps - signOut(this.auth) - .then(() => { - // signed out succcessfully TODO - }) - .catch((error) => { - // TODO - }); + return new Promise((resolve, reject) => { + signOut(this.auth) + .catch(reject); + }); } public login(username: string, password: string, remember_me: boolean) { diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 796b125e..67abe176 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -29,9 +29,13 @@ export class LocalLoginHandler extends LoginHandler { this.socket.emit("list_rooms", resultCallback); } - public logout() { - this._loginState = "not_authenticated"; - this.onReadyCallback(); + public logout(): Promise { + return new Promise((resolve, reject) => { + // reject(Error("LocalLoginHandler.logout() is not implemented")); + this._loginState = "not_authenticated"; + this.onReadyCallback(); + resolve(undefined); + }); } public login(username: string, password: string, remember_me: boolean) { diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index a0894280..3ced7e87 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -11,7 +11,7 @@ export abstract class LoginHandler { public abstract listRooms(resultCallback); - public abstract logout(); + public abstract logout(): Promise; public abstract login(username: string, password: string, remember_me: boolean); From b64a3f1cbef65327dbf70d952c2e0fcc72e3de7d Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 18:03:01 -0400 Subject: [PATCH 21/32] Login returns promise --- src/pages/home/tsx/components/LoginView.tsx | 5 ++++ .../login_handler/FirebaseLoginHandler.tsx | 28 +++++++++---------- .../tsx/login_handler/LocalLoginHandler.tsx | 10 ++++--- .../home/tsx/login_handler/LoginHandler.tsx | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index 345ed324..b6c9ff75 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -86,6 +86,11 @@ export const LoginView = (props) => { remember: data.get('remember') ? true : false, }; loginHandler.login(l['email'], l['password'], l['remember']) + .catch((error) => { + // https://stackoverflow.com/a/76014219/4753010 TODO(binit): handle failed login + setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); + setOpenFailureToast(true); + }); }; const validateInputs = () => { diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index 3bbd80f4..ea5b66d7 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -33,29 +33,28 @@ export class FirebaseLoginHandler extends LoginHandler { return new Promise((resolve, reject) => { signOut(this.auth) + .then(() => { resolve(undefined) }) .catch(reject); }); } - public login(username: string, password: string, remember_me: boolean) { + public login(username: string, password: string, remember_me: boolean): Promise { // Tutorial here: // https://firebase.google.com/docs/auth/web/start?hl=en#sign_in_existing_users // Auth State Persistence tutorial here: // https://firebase.google.com/docs/auth/web/auth-state-persistence - setPersistence(this.auth, remember_me ? browserLocalPersistence : browserSessionPersistence) - .then(() => { - signInWithEmailAndPassword(this.auth, username, password) - .then((userCredential) => { - // TODO - }) - .catch((sign_in_error) => { - // TODO - }); - }) - .catch((set_persistence_error) => { - // TODO - }); + return new Promise((resolve, reject) => { + setPersistence(this.auth, remember_me ? browserLocalPersistence : browserSessionPersistence) + .then(() => { + signInWithEmailAndPassword(this.auth, username, password) + .then((userCredential) => { + resolve(undefined); + }) + .catch(reject); + }) + .catch(reject); + }); } public forgot_password(username: string): Promise { @@ -64,6 +63,7 @@ export class FirebaseLoginHandler extends LoginHandler { return new Promise((resolve, reject) => { sendPasswordResetEmail(this.auth, username) + .then(() => { resolve(undefined) }) .catch(reject); }); } diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 67abe176..5fc54b83 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -31,16 +31,18 @@ export class LocalLoginHandler extends LoginHandler { public logout(): Promise { return new Promise((resolve, reject) => { - // reject(Error("LocalLoginHandler.logout() is not implemented")); this._loginState = "not_authenticated"; this.onReadyCallback(); resolve(undefined); }); } - public login(username: string, password: string, remember_me: boolean) { - this._loginState = "authenticated"; - this.onReadyCallback(); + public login(username: string, password: string, remember_me: boolean): Promise { + return new Promise((resolve, reject) => { + this._loginState = "authenticated"; + this.onReadyCallback(); + resolve(undefined); + }); } public forgot_password(username: string): Promise { diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index 3ced7e87..3b237bbd 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -13,7 +13,7 @@ export abstract class LoginHandler { public abstract logout(): Promise; - public abstract login(username: string, password: string, remember_me: boolean); + public abstract login(username: string, password: string, remember_me: boolean): Promise; public abstract forgot_password(username: string): Promise; } From e358cd37a0132495a5ad13361ab2947964dfb646 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 18:33:33 -0400 Subject: [PATCH 22/32] Handle incorrect login credentials --- src/pages/home/tsx/components/LoginView.tsx | 34 +++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index b6c9ff75..0a63e242 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -40,6 +40,15 @@ const Card = styled(MuiCard)(({ theme }) => ({ })); +const SignInError = styled(Box)(({ theme }) => ({ + backgroundColor: '#d32f2f', + margin: `calc(-1 * ${theme.spacing(4)})`, + marginBottom: 0, + padding: 10, + color: "white", +})); + + export const LoginView = (props) => { const [emailError, setEmailError] = useState(false); const [emailErrorMessage, setEmailErrorMessage] = useState(''); @@ -49,6 +58,7 @@ export const LoginView = (props) => { const [openToast, setOpenToast] = useState(false); const [openFailureToast, setOpenFailureToast] = useState(false); const [failureToastMessage, setfailureToastMessage] = useState(''); + const [failureLogin, setfailureLogin] = useState(false); const handleClickOpen = () => { setOpen(true); @@ -86,10 +96,21 @@ export const LoginView = (props) => { remember: data.get('remember') ? true : false, }; loginHandler.login(l['email'], l['password'], l['remember']) + .then(() => { + // reset failure messages + setfailureLogin(false); + setOpenFailureToast(false); + setfailureToastMessage(''); + }) .catch((error) => { - // https://stackoverflow.com/a/76014219/4753010 TODO(binit): handle failed login - setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); - setOpenFailureToast(true); + if (error.code === 'auth/user-not-found') { + setfailureLogin(true); + } else if (error.code === 'auth/invalid-email') { + setfailureLogin(true); + } else { + setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); + setOpenFailureToast(true); + } }); }; @@ -127,6 +148,13 @@ export const LoginView = (props) => { alignItems="center" minHeight="100vh"> + { failureLogin ? ( + + Incorrect email or password + + ) : ( + <> + )} Date: Wed, 23 Oct 2024 22:10:26 -0400 Subject: [PATCH 23/32] Firebase login/logout/passwd reset works --- src/pages/home/tsx/components/CallRobotSelector.tsx | 9 +++++---- src/pages/home/tsx/components/LoginView.tsx | 6 ++++-- src/pages/home/tsx/utils.tsx | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index e75f11d3..a4423789 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -115,10 +115,11 @@ export const CallRobotSelector = (props: { }) => { const [callableRobots, setCallableRobots] = useState([]); - const handleCRResult = (result) => { - setCallableRobots(result); - }; - loginHandler.listRooms(handleCRResult); + // TODO(binit) + // const handleCRResult = (result) => { + // setCallableRobots(result); + // }; + // loginHandler.listRooms(handleCRResult); return ( diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index 0a63e242..64cd41eb 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -103,7 +103,9 @@ export const LoginView = (props) => { setfailureToastMessage(''); }) .catch((error) => { - if (error.code === 'auth/user-not-found') { + if (error.code === 'auth/invalid-login-credentials') { + setfailureLogin(true); + } else if (error.code === 'auth/user-not-found') { setfailureLogin(true); } else if (error.code === 'auth/invalid-email') { setfailureLogin(true); @@ -204,7 +206,7 @@ export const LoginView = (props) => { error={passwordError} helperText={passwordErrorMessage} name="password" - placeholder="••••••" + placeholder="••••••••••••" type="password" id="password" autoComplete="current-password" diff --git a/src/pages/home/tsx/utils.tsx b/src/pages/home/tsx/utils.tsx index 8b935e9c..e8b92295 100644 --- a/src/pages/home/tsx/utils.tsx +++ b/src/pages/home/tsx/utils.tsx @@ -15,6 +15,7 @@ export function createLoginHandler(loginHandlerReadyCallback: () => void) { const config: FirebaseOptions = { apiKey: process.env.apiKey, authDomain: process.env.authDomain, + databaseURL: process.env.databaseURL, projectId: process.env.projectId, storageBucket: process.env.storageBucket, messagingSenderId: process.env.messagingSenderId, From 94670df847706f76ffa6a0ec843e82ac07ad2b66 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Wed, 23 Oct 2024 23:05:01 -0400 Subject: [PATCH 24/32] Doesnt work --- server.js | 6 +++--- .../home/tsx/components/CallRobotSelector.tsx | 16 ++++++++-------- src/pages/home/tsx/index.tsx | 2 ++ .../tsx/login_handler/FirebaseLoginHandler.tsx | 13 ++++++++++++- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/server.js b/server.js index 77a0f9d6..6dd183df 100644 --- a/server.js +++ b/server.js @@ -80,11 +80,11 @@ io.on("connection", function (socket) { s = "occupied" } - callback([{ - "roomid": process.env.HELLO_FLEET_ID, + callback({ "robot_id": { + "name": process.env.HELLO_FLEET_ID, "protocol": undefined, // TODO(binit): ensure robot/operator protocol match "status": s // ["online", "offline", "occupied"] - }]); + }}); }); socket.on("add operator to robot room", (callback) => { diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index a4423789..f40b2932 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -113,23 +113,23 @@ const CallRobotItem = (props: { export const CallRobotSelector = (props: { style?: React.CSSProperties; }) => { - const [callableRobots, setCallableRobots] = useState([]); + const [callableRobots, setCallableRobots] = useState({}); - // TODO(binit) - // const handleCRResult = (result) => { - // setCallableRobots(result); - // }; - // loginHandler.listRooms(handleCRResult); + // TODO(binit): doesn't work + const updateRooms = (result) => { + setCallableRobots(result); + }; + loginHandler.listRooms(updateRooms); return (

Robots:

{ - callableRobots.map((robot, idx) => { + Object.entries(callableRobots).map(([key, value], idx) => { return ( - + ) }) diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 1d27c5cb..6eedbace 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -17,6 +17,8 @@ loginHandler = createLoginHandler(loginHandlerReadyCallback); function renderHomePage() { + loginHandler.listRooms(() => {}); // TODO: remove + loginHandler.loginState() == "authenticated" ? document.title = "Home - Stretch Web Teleop" : document.title = "Login - Stretch Web Teleop"; diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index ea5b66d7..5e2013d6 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -1,19 +1,24 @@ import { LoginHandler } from "./LoginHandler"; import { initializeApp, FirebaseOptions } from "firebase/app"; import { getAuth, signInWithEmailAndPassword, onAuthStateChanged, sendPasswordResetEmail, signOut, setPersistence, browserLocalPersistence, browserSessionPersistence, Auth } from "firebase/auth"; +import { getDatabase, ref, onValue, Database } from "firebase/database"; export class FirebaseLoginHandler extends LoginHandler { private auth: Auth; private _loginState: string; + private db: Database; + private uid; string; constructor(onLoginHandlerReadyCallback: () => void, config: FirebaseOptions) { super(onLoginHandlerReadyCallback); this._loginState = "not_authenticated"; const app = initializeApp(config); this.auth = getAuth(app); + this.db = getDatabase(app); onAuthStateChanged(this.auth, (user) => { + this.uid = user.uid; this._loginState = user ? "authenticated" : "not_authenticated"; this.onReadyCallback(); }); @@ -24,7 +29,13 @@ export class FirebaseLoginHandler extends LoginHandler { } public listRooms(resultCallback) { - // TODO(binit) + if (this.uid === undefined) { + return; // TODO(binit): throw an exception? reject a promise? + } + + onValue(ref(this.db, 'rooms/' + this.uid + '/robots'), (snapshot) => { + resultCallback(snapshot.val()); + }); } public logout(): Promise { From c67cbe20b28f89118d49b1b9350d687ddd63a8d4 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 10:33:50 -0400 Subject: [PATCH 25/32] Fix for live update of callable robots --- src/pages/home/tsx/components/CallRobotSelector.tsx | 12 +++++++----- src/pages/home/tsx/index.tsx | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index f40b2932..393471b3 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -115,11 +115,13 @@ export const CallRobotSelector = (props: { }) => { const [callableRobots, setCallableRobots] = useState({}); - // TODO(binit): doesn't work - const updateRooms = (result) => { - setCallableRobots(result); - }; - loginHandler.listRooms(updateRooms); + useEffect(() => { + const updateRooms = (result) => { + console.log('updateRooms'); + setCallableRobots(result); + }; + loginHandler.listRooms(updateRooms); + },[props]); return ( diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 6eedbace..1d27c5cb 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -17,8 +17,6 @@ loginHandler = createLoginHandler(loginHandlerReadyCallback); function renderHomePage() { - loginHandler.listRooms(() => {}); // TODO: remove - loginHandler.loginState() == "authenticated" ? document.title = "Home - Stretch Web Teleop" : document.title = "Login - Stretch Web Teleop"; From 64ac96ce4f8f73fd88128ceb74a54dbf688dd6dd Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 11:53:00 -0400 Subject: [PATCH 26/32] Live update callable robots locally --- server.js | 28 +++++++++++++------ .../home/tsx/components/CallRobotSelector.tsx | 6 +--- .../tsx/login_handler/LocalLoginHandler.tsx | 3 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/server.js b/server.js index 6dd183df..c1a9dd27 100644 --- a/server.js +++ b/server.js @@ -54,6 +54,16 @@ io.on("connect_error", (err) => { console.log(`connect_error due to ${err.message}`); }); +let protocol = undefined; // TODO(binit): ensure robot/operator protocol match +let status = "offline"; // ["online", "offline", "occupied"] +function updateRooms() { + io.emit("update_rooms", { "robot_id": { + "name": process.env.HELLO_FLEET_ID, + "protocol": protocol, + "status": status + }}) +} + io.on("connection", function (socket) { console.log("new socket.io connection"); // console.log('socket.handshake = '); @@ -68,23 +78,20 @@ io.on("connection", function (socket) { ) { socket.join(room); socket.emit("join", room, socket.id); + status = "online"; } else { console.log("room full"); socket.emit("full", room); + status = "occupied"; } + updateRooms(); }); - socket.on("list_rooms", (callback) => { - let s = "online"; + socket.on("list_rooms", () => { if (io.sockets.adapter.rooms.get("robot") && io.sockets.adapter.rooms.get("robot").size >= 2) { - s = "occupied" + status = "occupied" } - - callback({ "robot_id": { - "name": process.env.HELLO_FLEET_ID, - "protocol": undefined, // TODO(binit): ensure robot/operator protocol match - "status": s // ["online", "offline", "occupied"] - }}); + updateRooms(); }); socket.on("add operator to robot room", (callback) => { @@ -98,10 +105,13 @@ io.on("connection", function (socket) { console.log("could not connect because robot room is full"); callback({ success: false }); } + status = "occupied"; } else { console.log("could not connect because robot is not available"); callback({ success: false }); + status = "offline"; } + updateRooms(); }); socket.on("signalling", function (message) { diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index 393471b3..75523f33 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -116,11 +116,7 @@ export const CallRobotSelector = (props: { const [callableRobots, setCallableRobots] = useState({}); useEffect(() => { - const updateRooms = (result) => { - console.log('updateRooms'); - setCallableRobots(result); - }; - loginHandler.listRooms(updateRooms); + loginHandler.listRooms(setCallableRobots); },[props]); return ( diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 5fc54b83..45ecb145 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -26,7 +26,8 @@ export class LocalLoginHandler extends LoginHandler { } public listRooms(resultCallback) { - this.socket.emit("list_rooms", resultCallback); + this.socket.emit("list_rooms"); + this.socket.on("update_rooms", resultCallback); } public logout(): Promise { From 66ac196fc783c12705fe3b40972528fb7aaa10a5 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 12:17:32 -0400 Subject: [PATCH 27/32] Fix auth may be null --- src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index 5e2013d6..c341a39a 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -8,7 +8,7 @@ export class FirebaseLoginHandler extends LoginHandler { private auth: Auth; private _loginState: string; private db: Database; - private uid; string; + private uid: string; constructor(onLoginHandlerReadyCallback: () => void, config: FirebaseOptions) { super(onLoginHandlerReadyCallback); @@ -18,7 +18,7 @@ export class FirebaseLoginHandler extends LoginHandler { this.db = getDatabase(app); onAuthStateChanged(this.auth, (user) => { - this.uid = user.uid; + this.uid = user ? user.uid : undefined; this._loginState = user ? "authenticated" : "not_authenticated"; this.onReadyCallback(); }); From bdc478f7b9e877f8467cd63eaac65ac91a82df45 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 12:35:05 -0400 Subject: [PATCH 28/32] Throw exception is uid null --- src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index c341a39a..2b683b6d 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -30,7 +30,7 @@ export class FirebaseLoginHandler extends LoginHandler { public listRooms(resultCallback) { if (this.uid === undefined) { - return; // TODO(binit): throw an exception? reject a promise? + throw new Error("FirebaseLoginHandler.listRooms(): this.uid is null"); } onValue(ref(this.db, 'rooms/' + this.uid + '/robots'), (snapshot) => { From cdc87133639aeb3edfbc292f2fd3376835fcc15d Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 12:36:49 -0400 Subject: [PATCH 29/32] whitespace --- .../home/tsx/components/CallRobotSelector.tsx | 2 +- src/pages/home/tsx/components/LoginView.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index 75523f33..e4755704 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -73,7 +73,6 @@ function get_indicator(status_str) { } - function get_action(status_str, robot_name) { switch (status_str) { case "online": @@ -87,6 +86,7 @@ function get_action(status_str, robot_name) { } } + const CallRobotItem = (props: { name: String; status: String; diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index 64cd41eb..e7c56f4b 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -123,21 +123,21 @@ export const LoginView = (props) => { let isValid = true; if (!email.value || !/\S+@\S+\.\S+/.test(email.value)) { - setEmailError(true); - setEmailErrorMessage('Please enter a valid email address.'); - isValid = false; + setEmailError(true); + setEmailErrorMessage('Please enter a valid email address.'); + isValid = false; } else { - setEmailError(false); - setEmailErrorMessage(''); + setEmailError(false); + setEmailErrorMessage(''); } if (!password.value || password.value.length < 6) { - setPasswordError(true); - setPasswordErrorMessage('Password must be at least 6 characters long.'); - isValid = false; + setPasswordError(true); + setPasswordErrorMessage('Password must be at least 6 characters long.'); + isValid = false; } else { - setPasswordError(false); - setPasswordErrorMessage(''); + setPasswordError(false); + setPasswordErrorMessage(''); } return isValid; From 40dfa5e9c4243af601620aa2746579cee1445772 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 15:57:28 -0400 Subject: [PATCH 30/32] Add production build npm option --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 952eb203..591a674c 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "scripts": { "firebase": "webpack --mode development --progress --env storage='firebase'", "localstorage": "webpack --mode development --progress --env storage='localstorage'", - "styleguide": "styleguidist server" + "styleguide": "styleguidist server", + "build": "webpack --mode production --progress --env storage='firebase'" }, "eslintConfig": { "extends": [ From 83a7f7705daa5d88e287ada1618e0658237e8714 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 16:00:17 -0400 Subject: [PATCH 31/32] Run prettier --- server.js | 19 +- src/pages/home/css/CallRobotSelector.css | 1 - src/pages/home/css/Changelog.css | 1 - src/pages/home/css/LoginView.css | 1 - src/pages/home/css/SideBySideView.css | 1 - .../home/tsx/components/CallRobotSelector.tsx | 152 ++++++---- src/pages/home/tsx/components/Changelog.tsx | 108 +++++-- .../home/tsx/components/ForgotPassword.tsx | 64 ++-- src/pages/home/tsx/components/LoginView.tsx | 276 ++++++++++-------- .../home/tsx/components/SideBySideView.tsx | 107 ++++--- src/pages/home/tsx/index.tsx | 23 +- .../login_handler/FirebaseLoginHandler.tsx | 45 ++- .../tsx/login_handler/LocalLoginHandler.tsx | 11 +- .../home/tsx/login_handler/LoginHandler.tsx | 8 +- src/pages/home/tsx/utils.tsx | 1 - webpack.config.js | 5 +- 16 files changed, 509 insertions(+), 314 deletions(-) diff --git a/server.js b/server.js index c1a9dd27..521a3678 100644 --- a/server.js +++ b/server.js @@ -57,11 +57,13 @@ io.on("connect_error", (err) => { let protocol = undefined; // TODO(binit): ensure robot/operator protocol match let status = "offline"; // ["online", "offline", "occupied"] function updateRooms() { - io.emit("update_rooms", { "robot_id": { - "name": process.env.HELLO_FLEET_ID, - "protocol": protocol, - "status": status - }}) + io.emit("update_rooms", { + robot_id: { + name: process.env.HELLO_FLEET_ID, + protocol: protocol, + status: status, + }, + }); } io.on("connection", function (socket) { @@ -88,8 +90,11 @@ io.on("connection", function (socket) { }); socket.on("list_rooms", () => { - if (io.sockets.adapter.rooms.get("robot") && io.sockets.adapter.rooms.get("robot").size >= 2) { - status = "occupied" + if ( + io.sockets.adapter.rooms.get("robot") && + io.sockets.adapter.rooms.get("robot").size >= 2 + ) { + status = "occupied"; } updateRooms(); }); diff --git a/src/pages/home/css/CallRobotSelector.css b/src/pages/home/css/CallRobotSelector.css index 339e1d4c..e281b5a2 100644 --- a/src/pages/home/css/CallRobotSelector.css +++ b/src/pages/home/css/CallRobotSelector.css @@ -1,4 +1,3 @@ - .rs-container { overflow: auto; } diff --git a/src/pages/home/css/Changelog.css b/src/pages/home/css/Changelog.css index 75edf24e..27aabb7c 100644 --- a/src/pages/home/css/Changelog.css +++ b/src/pages/home/css/Changelog.css @@ -1,4 +1,3 @@ - .cv-container { overflow: auto; } diff --git a/src/pages/home/css/LoginView.css b/src/pages/home/css/LoginView.css index 34bc3344..0e8a0cf0 100644 --- a/src/pages/home/css/LoginView.css +++ b/src/pages/home/css/LoginView.css @@ -1,4 +1,3 @@ - .lv-container { padding: 20px; } diff --git a/src/pages/home/css/SideBySideView.css b/src/pages/home/css/SideBySideView.css index 641879cd..d5478902 100644 --- a/src/pages/home/css/SideBySideView.css +++ b/src/pages/home/css/SideBySideView.css @@ -1,4 +1,3 @@ - .sbs-container { padding: 20px; } diff --git a/src/pages/home/tsx/components/CallRobotSelector.tsx b/src/pages/home/tsx/components/CallRobotSelector.tsx index e4755704..01a40c6a 100644 --- a/src/pages/home/tsx/components/CallRobotSelector.tsx +++ b/src/pages/home/tsx/components/CallRobotSelector.tsx @@ -1,137 +1,167 @@ import "home/css/CallRobotSelector.css"; import React, { useEffect, useState } from "react"; -import Box from '@mui/material/Box'; -import Grid from '@mui/material/Grid2'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import CardActions from '@mui/material/CardActions'; -import Button from '@mui/material/Button'; -import Typography from '@mui/material/Typography'; +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid2"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardActions from "@mui/material/CardActions"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; import CircleIcon from "@mui/icons-material/Circle"; -import { green, red, yellow, grey } from '@mui/material/colors'; +import { green, red, yellow, grey } from "@mui/material/colors"; import { loginHandler } from "../index"; - function get_indicator_text(status_str) { switch (status_str) { case "online": - return "Online" + return "Online"; case "offline": - return "Offline" + return "Offline"; case "occupied": - return "Occupied" + return "Occupied"; default: - return "Unknown" + return "Unknown"; } } - function get_indicator(status_str) { let statusui; switch (status_str) { case "online": statusui = { - "color_name": "green", - "color": green, + color_name: "green", + color: green, }; break; case "offline": statusui = { - "color_name": "red", - "color": red, + color_name: "red", + color: red, }; break; case "occupied": statusui = { - "color_name": "yellow", - "color": yellow, + color_name: "yellow", + color: yellow, }; break; default: statusui = { - "color_name": "grey", - "color": grey, + color_name: "grey", + color: grey, }; } let indicator_css = { fontSize: 12, - color: statusui['color']["A400"], - animation: `glowing_${statusui['color_name']} 3s linear infinite` + color: statusui["color"]["A400"], + animation: `glowing_${statusui["color_name"]} 3s linear infinite`, }; - indicator_css[`@keyframes glowing_${statusui['color_name']}`] = { + indicator_css[`@keyframes glowing_${statusui["color_name"]}`] = { "0%": { - color: statusui['color']["A400"] + color: statusui["color"]["A400"], }, "50%": { - color: statusui['color']["A200"] + color: statusui["color"]["A200"], }, "100%": { - color: statusui['color']["A400"] - } + color: statusui["color"]["A400"], + }, }; - return + return ; } - function get_action(status_str, robot_name) { switch (status_str) { case "online": - return + return ( + + ); case "offline": - return + return ( + + ); case "occupied": - return + return ( + + ); default: - return + return ( + + ); } } - -const CallRobotItem = (props: { - name: String; - status: String; -}) => { - +const CallRobotItem = (props: { name: String; status: String }) => { return ( {props.name} - - {get_indicator(props.status)} {get_indicator_text(props.status)} + + {get_indicator(props.status)}{" "} + {get_indicator_text(props.status)} - - {get_action(props.status, props.name)} - + {get_action(props.status, props.name)} ); }; - -export const CallRobotSelector = (props: { - style?: React.CSSProperties; -}) => { +export const CallRobotSelector = (props: { style?: React.CSSProperties }) => { const [callableRobots, setCallableRobots] = useState({}); useEffect(() => { loginHandler.listRooms(setCallableRobots); - },[props]); + }, [props]); return (

Robots:

- - { - Object.entries(callableRobots).map(([key, value], idx) => { - return ( - - - - ) - }) - } + + {Object.entries(callableRobots).map(([key, value], idx) => { + return ( + + + + ); + })}
); diff --git a/src/pages/home/tsx/components/Changelog.tsx b/src/pages/home/tsx/components/Changelog.tsx index df545a85..c95b5f9c 100644 --- a/src/pages/home/tsx/components/Changelog.tsx +++ b/src/pages/home/tsx/components/Changelog.tsx @@ -1,9 +1,8 @@ import "home/css/Changelog.css"; import React, { useEffect, useState } from "react"; -import Box from '@mui/material/Box'; -import Grid from '@mui/material/Grid2'; -import { styled } from '@mui/material/styles'; - +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid2"; +import { styled } from "@mui/material/styles"; const LogItem = styled(Box)(({ theme }) => ({ ...theme.typography.body2, @@ -12,43 +11,112 @@ const LogItem = styled(Box)(({ theme }) => ({ fontSize: 17, })); - -export const Changelog = (props: { - style?: React.CSSProperties; -}) => { - +export const Changelog = (props: { style?: React.CSSProperties }) => { return (

What's new?

- + - Login Page - Oct 9th, 2024 -

The new homepage shows the robots you can call, or as "unavailable" if the robot is powered off or occupied by another operator. There's also a "What's New" section with details about new features being added to the web interface. By default, you are logged in when running the interface locally, but there's a login screen that can be accessed by logging out. In the future, this login screen will enable you to call your Stretch over the internet.

+ + Login Page - + Oct 9th, 2024 + +

+ The new homepage shows the robots you can call, or + as "unavailable" if the robot is powered off or + occupied by another operator. There's also a "What's + New" section with details about new features being + added to the web interface. By default, you are + logged in when running the interface locally, but + there's a login screen that can be accessed by + logging out. In the future, this login screen will + enable you to call your Stretch over the internet. +

- Homing Button - Sept 21st, 2024 -

The operator interface now shows an banner if your robot needs to be homed. Since some of Stretch's encoders are relative, there's a homing sequence to find zero for those joints when Stretch wakes up. Previously, developers had to use a terminal to trigger Stretch's homing sequence, but now you can do it through the web interface.

+ + + Homing Button + {" "} + - Sept 21st, 2024 + +

+ The operator interface now shows an banner if your + robot needs to be homed. Since some of Stretch's + encoders are relative, there's a homing sequence to + find zero for those joints when Stretch wakes up. + Previously, developers had to use a terminal to + trigger Stretch's homing sequence, but now you can + do it through the web interface. +

- Lorem ipsum dolor et. - May 18st, 2024 -

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. Feugiat fringilla tristique varius feugiat quis cras magnis efficitur. Aptent curabitur mattis dui congue porta cubilia. Lorem scelerisque convallis tempor himenaeos donec inceptos ultricies dis. Efficitur feugiat senectus nullam semper conubia risus mi volutpat.

+ + + Lorem ipsum dolor et. + {" "} + - May 18st, 2024 + +

+ Lorem ipsum odor amet, consectetuer adipiscing elit. + Mattis purus potenti orci per torquent scelerisque. + Feugiat fringilla tristique varius feugiat quis cras + magnis efficitur. Aptent curabitur mattis dui congue + porta cubilia. Lorem scelerisque convallis tempor + himenaeos donec inceptos ultricies dis. Efficitur + feugiat senectus nullam semper conubia risus mi + volutpat. +

- Lorem ipsum dolor et. - Mar 2nd, 2024 -

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. Feugiat fringilla tristique varius feugiat quis cras magnis efficitur. Aptent curabitur mattis dui congue porta cubilia. Lorem scelerisque convallis tempor himenaeos donec inceptos ultricies dis. Efficitur feugiat senectus nullam semper conubia risus mi volutpat.

+ + + Lorem ipsum dolor et. + {" "} + - Mar 2nd, 2024 + +

+ Lorem ipsum odor amet, consectetuer adipiscing elit. + Mattis purus potenti orci per torquent scelerisque. + Feugiat fringilla tristique varius feugiat quis cras + magnis efficitur. Aptent curabitur mattis dui congue + porta cubilia. Lorem scelerisque convallis tempor + himenaeos donec inceptos ultricies dis. Efficitur + feugiat senectus nullam semper conubia risus mi + volutpat. +

- Lorem ipsum dolor et. - Jan 31st, 2024 -

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. Feugiat fringilla tristique varius feugiat quis cras magnis efficitur. Aptent curabitur mattis dui congue porta cubilia. Lorem scelerisque convallis tempor himenaeos donec inceptos ultricies dis. Efficitur feugiat senectus nullam semper conubia risus mi volutpat.

+ + + Lorem ipsum dolor et. + {" "} + - Jan 31st, 2024 + +

+ Lorem ipsum odor amet, consectetuer adipiscing elit. + Mattis purus potenti orci per torquent scelerisque. + Feugiat fringilla tristique varius feugiat quis cras + magnis efficitur. Aptent curabitur mattis dui congue + porta cubilia. Lorem scelerisque convallis tempor + himenaeos donec inceptos ultricies dis. Efficitur + feugiat senectus nullam semper conubia risus mi + volutpat. +

diff --git a/src/pages/home/tsx/components/ForgotPassword.tsx b/src/pages/home/tsx/components/ForgotPassword.tsx index 248e07dc..5ea11e3c 100644 --- a/src/pages/home/tsx/components/ForgotPassword.tsx +++ b/src/pages/home/tsx/components/ForgotPassword.tsx @@ -3,52 +3,56 @@ import React, { useEffect, useState } from "react"; import { isTablet, isBrowser } from "react-device-detect"; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; -import DialogActions from '@mui/material/DialogActions'; -import OutlinedInput from '@mui/material/OutlinedInput'; -import Button from '@mui/material/Button'; - +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; +import OutlinedInput from "@mui/material/OutlinedInput"; +import Button from "@mui/material/Button"; export const ForgotPassword = (props: { open: boolean; handleClose: () => void; handleExecute: (email: string) => void; }) => { - return isTablet || isBrowser ? ( ) => { - event.preventDefault(); - const data = new FormData(event.currentTarget); - props.handleExecute(data.get('email') as string); - props.handleClose(); - event.stopPropagation(); - }}}> + open={props.open} + onClose={props.handleClose} + PaperProps={{ + component: "form", + onSubmit: (event: React.FormEvent) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + props.handleExecute(data.get("email") as string); + props.handleClose(); + event.stopPropagation(); + }, + }} + > Reset password - Enter your account's email address, and we'll send you a link to reset your password. + Enter your account's email address, and we'll send + you a link to reset your password. + autoFocus + required + margin="dense" + id="email" + name="email" + placeholder="Email address" + type="email" + fullWidth + /> - + ) : ( diff --git a/src/pages/home/tsx/components/LoginView.tsx b/src/pages/home/tsx/components/LoginView.tsx index e7c56f4b..0e96d073 100644 --- a/src/pages/home/tsx/components/LoginView.tsx +++ b/src/pages/home/tsx/components/LoginView.tsx @@ -4,60 +4,57 @@ import "home/css/LoginView.css"; import React, { useEffect, useState } from "react"; import { isTablet, isBrowser } from "react-device-detect"; -import Box from '@mui/material/Box'; -import MuiCard from '@mui/material/Card'; -import Typography from '@mui/material/Typography'; -import FormControl from '@mui/material/FormControl'; -import FormLabel from '@mui/material/FormLabel'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import TextField from '@mui/material/TextField'; -import Link from '@mui/material/Link'; -import Checkbox from '@mui/material/Checkbox'; -import Button from '@mui/material/Button'; -import Snackbar from '@mui/material/Snackbar'; -import { styled } from '@mui/material/styles'; +import Box from "@mui/material/Box"; +import MuiCard from "@mui/material/Card"; +import Typography from "@mui/material/Typography"; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import TextField from "@mui/material/TextField"; +import Link from "@mui/material/Link"; +import Checkbox from "@mui/material/Checkbox"; +import Button from "@mui/material/Button"; +import Snackbar from "@mui/material/Snackbar"; +import { styled } from "@mui/material/styles"; import { ForgotPassword } from "./ForgotPassword"; import { loginHandler } from "../index"; - const Card = styled(MuiCard)(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - alignSelf: 'center', - width: '100%', + display: "flex", + flexDirection: "column", + alignSelf: "center", + width: "100%", padding: theme.spacing(4), gap: theme.spacing(2), - margin: 'auto', - [theme.breakpoints.up('sm')]: { - maxWidth: '450px', + margin: "auto", + [theme.breakpoints.up("sm")]: { + maxWidth: "450px", }, boxShadow: - 'hsla(220, 30%, 5%, 0.05) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.05) 0px 15px 35px -5px', - ...theme.applyStyles('dark', { - boxShadow: - 'hsla(220, 30%, 5%, 0.5) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.08) 0px 15px 35px -5px', + "hsla(220, 30%, 5%, 0.05) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.05) 0px 15px 35px -5px", + ...theme.applyStyles("dark", { + boxShadow: + "hsla(220, 30%, 5%, 0.5) 0px 5px 15px 0px, hsla(220, 25%, 10%, 0.08) 0px 15px 35px -5px", }), })); - const SignInError = styled(Box)(({ theme }) => ({ - backgroundColor: '#d32f2f', + backgroundColor: "#d32f2f", margin: `calc(-1 * ${theme.spacing(4)})`, marginBottom: 0, padding: 10, color: "white", })); - export const LoginView = (props) => { const [emailError, setEmailError] = useState(false); - const [emailErrorMessage, setEmailErrorMessage] = useState(''); + const [emailErrorMessage, setEmailErrorMessage] = useState(""); const [passwordError, setPasswordError] = useState(false); - const [passwordErrorMessage, setPasswordErrorMessage] = useState(''); + const [passwordErrorMessage, setPasswordErrorMessage] = useState(""); const [open, setOpen] = useState(false); const [openToast, setOpenToast] = useState(false); const [openFailureToast, setOpenFailureToast] = useState(false); - const [failureToastMessage, setfailureToastMessage] = useState(''); + const [failureToastMessage, setfailureToastMessage] = useState(""); const [failureLogin, setfailureLogin] = useState(false); const handleClickOpen = () => { @@ -65,12 +62,15 @@ export const LoginView = (props) => { }; const handleForgotPassword = (email: string) => { - loginHandler.forgot_password(email) + loginHandler + .forgot_password(email) .then(() => { setOpenToast(true); }) .catch((error) => { - setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); + setfailureToastMessage( + `Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`, + ); setOpenFailureToast(true); }); }; @@ -91,158 +91,192 @@ export const LoginView = (props) => { const data = new FormData(event.currentTarget); let l = { - email: data.get('email') as string, - password: data.get('password') as string, - remember: data.get('remember') ? true : false, + email: data.get("email") as string, + password: data.get("password") as string, + remember: data.get("remember") ? true : false, }; - loginHandler.login(l['email'], l['password'], l['remember']) + loginHandler + .login(l["email"], l["password"], l["remember"]) .then(() => { // reset failure messages setfailureLogin(false); setOpenFailureToast(false); - setfailureToastMessage(''); + setfailureToastMessage(""); }) .catch((error) => { - if (error.code === 'auth/invalid-login-credentials') { + if (error.code === "auth/invalid-login-credentials") { setfailureLogin(true); - } else if (error.code === 'auth/user-not-found') { + } else if (error.code === "auth/user-not-found") { setfailureLogin(true); - } else if (error.code === 'auth/invalid-email') { + } else if (error.code === "auth/invalid-email") { setfailureLogin(true); } else { - setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); + setfailureToastMessage( + `Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`, + ); setOpenFailureToast(true); } }); }; const validateInputs = () => { - const email = document.getElementById('email') as HTMLInputElement; - const password = document.getElementById('password') as HTMLInputElement; - + const email = document.getElementById("email") as HTMLInputElement; + const password = document.getElementById( + "password", + ) as HTMLInputElement; + let isValid = true; - + if (!email.value || !/\S+@\S+\.\S+/.test(email.value)) { setEmailError(true); - setEmailErrorMessage('Please enter a valid email address.'); + setEmailErrorMessage("Please enter a valid email address."); isValid = false; } else { setEmailError(false); - setEmailErrorMessage(''); + setEmailErrorMessage(""); } - + if (!password.value || password.value.length < 6) { setPasswordError(true); - setPasswordErrorMessage('Password must be at least 6 characters long.'); + setPasswordErrorMessage( + "Password must be at least 6 characters long.", + ); isValid = false; } else { setPasswordError(false); - setPasswordErrorMessage(''); + setPasswordErrorMessage(""); } - + return isValid; }; return isTablet || isBrowser ? ( + display="flex" + justifyContent="center" + alignItems="center" + minHeight="100vh" + > - { failureLogin ? ( - - Incorrect email or password - + {failureLogin ? ( + Incorrect email or password ) : ( <> )} + component="h1" + variant="h4" + sx={{ + width: "100%", + fontSize: "clamp(2rem, 10vw, 2.15rem)", + }} + > Sign in + component="form" + onSubmit={handleSubmit} + noValidate + sx={{ + display: "flex", + flexDirection: "column", + width: "100%", + gap: 2, + }} + > Email + error={emailError} + helperText={emailErrorMessage} + id="email" + type="email" + name="email" + placeholder="your@email.com" + autoComplete="email" + autoFocus + required + fullWidth + variant="outlined" + color={emailError ? "error" : "primary"} + sx={{ ariaLabel: "email" }} + /> - + Password + component="button" + type="button" + onClick={handleClickOpen} + variant="body2" + sx={{ alignSelf: "baseline" }} + > Forgot your password? + error={passwordError} + helperText={passwordErrorMessage} + name="password" + placeholder="••••••••••••" + type="password" + id="password" + autoComplete="current-password" + required + fullWidth + variant="outlined" + color={passwordError ? "error" : "primary"} + /> } - label="Remember me" /> - + control={ + + } + label="Remember me" + /> + + anchorOrigin={{ vertical: "bottom", horizontal: "center" }} + open={openToast} + onClose={handleToastClose} + autoHideDuration={6000} + message="We'll send you a password reset email soon" + /> + anchorOrigin={{ vertical: "bottom", horizontal: "center" }} + open={openFailureToast} + message={failureToastMessage} + ContentProps={{ + sx: { + background: "red", + }, + }} + /> ) : (

Not implemented

diff --git a/src/pages/home/tsx/components/SideBySideView.tsx b/src/pages/home/tsx/components/SideBySideView.tsx index 517ffe7c..1222d1fe 100644 --- a/src/pages/home/tsx/components/SideBySideView.tsx +++ b/src/pages/home/tsx/components/SideBySideView.tsx @@ -1,85 +1,112 @@ import "home/css/SideBySideView.css"; import React, { useEffect, useState } from "react"; import { isTablet, isBrowser } from "react-device-detect"; -import Box from '@mui/material/Box'; -import Grid from '@mui/material/Grid2'; -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import Snackbar from '@mui/material/Snackbar'; +import Box from "@mui/material/Box"; +import Grid from "@mui/material/Grid2"; +import AppBar from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; +import Typography from "@mui/material/Typography"; +import Button from "@mui/material/Button"; +import Snackbar from "@mui/material/Snackbar"; import { Changelog } from "./Changelog"; import { CallRobotSelector } from "./CallRobotSelector"; import { loginHandler } from "../index"; - export const SideBySideView = (props) => { const [openFailureToast, setOpenFailureToast] = useState(false); - const [failureToastMessage, setfailureToastMessage] = useState(''); + const [failureToastMessage, setfailureToastMessage] = useState(""); const handleLogout = () => { - loginHandler.logout() - .catch((error) => { - setfailureToastMessage(`Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`); - setOpenFailureToast(true); - }); + loginHandler.logout().catch((error) => { + setfailureToastMessage( + `Please contact Hello Robot Support. ERROR ${error.code}: ${error.message}`, + ); + setOpenFailureToast(true); + }); }; return isTablet || isBrowser ? ( - + Stretch Web Teleop - + - + - + - + + anchorOrigin={{ vertical: "bottom", horizontal: "center" }} + open={openFailureToast} + message={failureToastMessage} + ContentProps={{ + sx: { + background: "red", + }, + }} + /> ) : ( - + Stretch Web Teleop - + - + - + - + + anchorOrigin={{ vertical: "bottom", horizontal: "center" }} + open={openFailureToast} + message={failureToastMessage} + ContentProps={{ + sx: { + background: "red", + }, + }} + /> ); }; diff --git a/src/pages/home/tsx/index.tsx b/src/pages/home/tsx/index.tsx index 1d27c5cb..536c14cc 100644 --- a/src/pages/home/tsx/index.tsx +++ b/src/pages/home/tsx/index.tsx @@ -1,10 +1,10 @@ -import 'home/css/index.css'; +import "home/css/index.css"; import React from "react"; import { createRoot } from "react-dom/client"; -import { createLoginHandler } from './utils'; -import { LoginHandler } from './login_handler/LoginHandler'; -import { SideBySideView } from './components/SideBySideView'; -import { LoginView } from './components/LoginView'; +import { createLoginHandler } from "./utils"; +import { LoginHandler } from "./login_handler/LoginHandler"; +import { SideBySideView } from "./components/SideBySideView"; +import { LoginView } from "./components/LoginView"; export let loginHandler: LoginHandler; const container = document.getElementById("root"); @@ -15,17 +15,12 @@ const loginHandlerReadyCallback = () => { }; loginHandler = createLoginHandler(loginHandlerReadyCallback); - function renderHomePage() { loginHandler.loginState() == "authenticated" - ? document.title = "Home - Stretch Web Teleop" - : document.title = "Login - Stretch Web Teleop"; + ? (document.title = "Home - Stretch Web Teleop") + : (document.title = "Login - Stretch Web Teleop"); loginHandler.loginState() == "authenticated" - ? root.render( - - ) - : root.render( - - ); + ? root.render() + : root.render(); } diff --git a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx index 2b683b6d..9b3f726f 100644 --- a/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/FirebaseLoginHandler.tsx @@ -1,16 +1,28 @@ import { LoginHandler } from "./LoginHandler"; import { initializeApp, FirebaseOptions } from "firebase/app"; -import { getAuth, signInWithEmailAndPassword, onAuthStateChanged, sendPasswordResetEmail, signOut, setPersistence, browserLocalPersistence, browserSessionPersistence, Auth } from "firebase/auth"; +import { + getAuth, + signInWithEmailAndPassword, + onAuthStateChanged, + sendPasswordResetEmail, + signOut, + setPersistence, + browserLocalPersistence, + browserSessionPersistence, + Auth, +} from "firebase/auth"; import { getDatabase, ref, onValue, Database } from "firebase/database"; - export class FirebaseLoginHandler extends LoginHandler { private auth: Auth; private _loginState: string; private db: Database; private uid: string; - constructor(onLoginHandlerReadyCallback: () => void, config: FirebaseOptions) { + constructor( + onLoginHandlerReadyCallback: () => void, + config: FirebaseOptions, + ) { super(onLoginHandlerReadyCallback); this._loginState = "not_authenticated"; const app = initializeApp(config); @@ -30,10 +42,12 @@ export class FirebaseLoginHandler extends LoginHandler { public listRooms(resultCallback) { if (this.uid === undefined) { - throw new Error("FirebaseLoginHandler.listRooms(): this.uid is null"); + throw new Error( + "FirebaseLoginHandler.listRooms(): this.uid is null", + ); } - onValue(ref(this.db, 'rooms/' + this.uid + '/robots'), (snapshot) => { + onValue(ref(this.db, "rooms/" + this.uid + "/robots"), (snapshot) => { resultCallback(snapshot.val()); }); } @@ -44,19 +58,30 @@ export class FirebaseLoginHandler extends LoginHandler { return new Promise((resolve, reject) => { signOut(this.auth) - .then(() => { resolve(undefined) }) + .then(() => { + resolve(undefined); + }) .catch(reject); }); } - public login(username: string, password: string, remember_me: boolean): Promise { + public login( + username: string, + password: string, + remember_me: boolean, + ): Promise { // Tutorial here: // https://firebase.google.com/docs/auth/web/start?hl=en#sign_in_existing_users // Auth State Persistence tutorial here: // https://firebase.google.com/docs/auth/web/auth-state-persistence return new Promise((resolve, reject) => { - setPersistence(this.auth, remember_me ? browserLocalPersistence : browserSessionPersistence) + setPersistence( + this.auth, + remember_me + ? browserLocalPersistence + : browserSessionPersistence, + ) .then(() => { signInWithEmailAndPassword(this.auth, username, password) .then((userCredential) => { @@ -74,7 +99,9 @@ export class FirebaseLoginHandler extends LoginHandler { return new Promise((resolve, reject) => { sendPasswordResetEmail(this.auth, username) - .then(() => { resolve(undefined) }) + .then(() => { + resolve(undefined); + }) .catch(reject); }); } diff --git a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx index 45ecb145..4d4920b9 100644 --- a/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LocalLoginHandler.tsx @@ -1,7 +1,6 @@ import { LoginHandler } from "./LoginHandler"; import io, { Socket } from "socket.io-client"; - export class LocalLoginHandler extends LoginHandler { private socket: Socket; private _loginState: string; @@ -38,7 +37,11 @@ export class LocalLoginHandler extends LoginHandler { }); } - public login(username: string, password: string, remember_me: boolean): Promise { + public login( + username: string, + password: string, + remember_me: boolean, + ): Promise { return new Promise((resolve, reject) => { this._loginState = "authenticated"; this.onReadyCallback(); @@ -48,7 +51,9 @@ export class LocalLoginHandler extends LoginHandler { public forgot_password(username: string): Promise { return new Promise((resolve, reject) => { - reject(Error("LocalLoginHandler.forgot_password() is not implemented")); + reject( + Error("LocalLoginHandler.forgot_password() is not implemented"), + ); // resolve(undefined); }); } diff --git a/src/pages/home/tsx/login_handler/LoginHandler.tsx b/src/pages/home/tsx/login_handler/LoginHandler.tsx index 3b237bbd..ad875377 100644 --- a/src/pages/home/tsx/login_handler/LoginHandler.tsx +++ b/src/pages/home/tsx/login_handler/LoginHandler.tsx @@ -1,5 +1,3 @@ - - export abstract class LoginHandler { public onReadyCallback: () => void; @@ -13,7 +11,11 @@ export abstract class LoginHandler { public abstract logout(): Promise; - public abstract login(username: string, password: string, remember_me: boolean): Promise; + public abstract login( + username: string, + password: string, + remember_me: boolean, + ): Promise; public abstract forgot_password(username: string): Promise; } diff --git a/src/pages/home/tsx/utils.tsx b/src/pages/home/tsx/utils.tsx index e8b92295..65ed5123 100644 --- a/src/pages/home/tsx/utils.tsx +++ b/src/pages/home/tsx/utils.tsx @@ -2,7 +2,6 @@ import { FirebaseOptions } from "firebase/app"; import { FirebaseLoginHandler } from "./login_handler/FirebaseLoginHandler"; import { LocalLoginHandler } from "./login_handler/LocalLoginHandler"; - /** * Creates a login handler based on the `storage` property in the process * environment. diff --git a/webpack.config.js b/webpack.config.js index 060480b7..57dc6555 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -52,7 +52,10 @@ module.exports = (env) => { new HtmlWebpackPlugin({ inject: true, template: `./src/pages/${page}/html/index.html`, - filename: page == "home" ? "index.html" : `${page}/index.html`, + filename: + page == "home" + ? "index.html" + : `${page}/index.html`, chunks: [page], }), ), From 4ba84f139a6255ee93eccb523c97ee9bd42e1830 Mon Sep 17 00:00:00 2001 From: Binit Shah Date: Thu, 24 Oct 2024 16:01:54 -0400 Subject: [PATCH 32/32] Run prettier --- src/pages/home/tsx/components/Changelog.tsx | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pages/home/tsx/components/Changelog.tsx b/src/pages/home/tsx/components/Changelog.tsx index c95b5f9c..1894242a 100644 --- a/src/pages/home/tsx/components/Changelog.tsx +++ b/src/pages/home/tsx/components/Changelog.tsx @@ -70,12 +70,12 @@ export const Changelog = (props: { style?: React.CSSProperties }) => {

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. - Feugiat fringilla tristique varius feugiat quis cras - magnis efficitur. Aptent curabitur mattis dui congue - porta cubilia. Lorem scelerisque convallis tempor - himenaeos donec inceptos ultricies dis. Efficitur - feugiat senectus nullam semper conubia risus mi - volutpat. + Feugiat fringilla tristique various feugiat quis + cras magnis efficitur. Aptent curabitur mattis dui + congue porta cubilia. Lorem scelerisque convallis + tempor himenaeos donec inceptos ultricies dis. + Efficitur feugiat senectus nullam semper conubia + risus mi volutpat.

@@ -90,12 +90,12 @@ export const Changelog = (props: { style?: React.CSSProperties }) => {

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. - Feugiat fringilla tristique varius feugiat quis cras - magnis efficitur. Aptent curabitur mattis dui congue - porta cubilia. Lorem scelerisque convallis tempor - himenaeos donec inceptos ultricies dis. Efficitur - feugiat senectus nullam semper conubia risus mi - volutpat. + Feugiat fringilla tristique various feugiat quis + cras magnis efficitur. Aptent curabitur mattis dui + congue porta cubilia. Lorem scelerisque convallis + tempor himenaeos donec inceptos ultricies dis. + Efficitur feugiat senectus nullam semper conubia + risus mi volutpat.

@@ -110,12 +110,12 @@ export const Changelog = (props: { style?: React.CSSProperties }) => {

Lorem ipsum odor amet, consectetuer adipiscing elit. Mattis purus potenti orci per torquent scelerisque. - Feugiat fringilla tristique varius feugiat quis cras - magnis efficitur. Aptent curabitur mattis dui congue - porta cubilia. Lorem scelerisque convallis tempor - himenaeos donec inceptos ultricies dis. Efficitur - feugiat senectus nullam semper conubia risus mi - volutpat. + Feugiat fringilla tristique various feugiat quis + cras magnis efficitur. Aptent curabitur mattis dui + congue porta cubilia. Lorem scelerisque convallis + tempor himenaeos donec inceptos ultricies dis. + Efficitur feugiat senectus nullam semper conubia + risus mi volutpat.