diff --git a/.gitignore b/.gitignore index 4d7139e..68563dc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ # Extra Stuff /vscode/ + + +# Build directories +dist/ \ No newline at end of file diff --git a/test-app/.babelrc b/test-app/.babelrc new file mode 100644 index 0000000..18151f1 --- /dev/null +++ b/test-app/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} \ No newline at end of file diff --git a/test-app/package.json b/test-app/package.json index 6d84b16..adf5ea9 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -3,15 +3,34 @@ "version": "2.0.0", "main": "src/main.js", "scripts": { - "start": "electron ." + "start": "electron .", + "build": "webpack --mode production", + "dev": "webpack --mode production && electron ." }, "dependencies": { - "rclnodejs": "^0.27.4", - "fitty": "^2.3.6" + "fitty": "^2.3.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.8.2" }, "devDependencies": { + "@babel/core": "^7.28.3", + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", "@electron/rebuild": "^3.6.0", + "autoprefixer": "^10.4.21", + "babel-loader": "^10.0.0", + "concurrently": "^9.2.1", + "css-loader": "^7.1.2", "electron": "^31.0.0", - "material-icons": "^1.13.9" + "html-webpack-plugin": "^5.6.4", + "material-icons": "^1.13.9", + "postcss": "^8.5.6", + "postcss-loader": "^8.2.0", + "style-loader": "^4.0.0", + "wait-on": "^8.0.4", + "webpack": "^5.101.3", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.2" } } diff --git a/test-app/src/lock_screen.html b/test-app/src/lock_screen.html index acefd4c..a55f16f 100644 --- a/test-app/src/lock_screen.html +++ b/test-app/src/lock_screen.html @@ -72,7 +72,6 @@ font-size: 125%; } .btn.pressed { - transform: scale(1.05); opacity: 0.8; } /* .shutdown { @@ -149,20 +148,8 @@ border-radius: 50%; background: rgb(95, 95, 95); margin: 0px 5px; - animation: scale 0.3s ease-in-out; /* Animation! */ - } - @keyframes scale { - from { - transform: scale(1.4); - } } .dot.removing { - animation: scaleIn 0.15s ease-in-out; - } - @keyframes scaleIn { - to { - transform: scale(0); - } } diff --git a/test-app/src/main.js b/test-app/src/main.js index 226b153..961c018 100644 --- a/test-app/src/main.js +++ b/test-app/src/main.js @@ -1,71 +1,86 @@ -const { app, BrowserWindow, ipcMain } = require('electron'); -const path = require('node:path'); +const { app, BrowserWindow, ipcMain } = require("electron"); +const path = require("node:path"); app.disableHardwareAcceleration(); // Disable GPU acceleration //-------------------------------------------------------------------------------------------------- // ROS 2 Setup Stuff -const rclnodejs = require('rclnodejs'); -let node = null; +// const rclnodejs = require('rclnodejs'); +// let node = null; let publisher_light, publisher_brake, publisher_lidar, publisher_selfdrive; +// Track current status of each system +let systemStatus = { + light: 'OFF', + brake: 'ON', + lidar: 'OFF', + selfdrive: 'OFF' +}; + async function initROS() { - await rclnodejs.init(); + console.log('ROS initialization disabled for testing'); + // All ROS functionality is commented out since rclnodejs is not available + // await rclnodejs.init(); // Create node - node = new rclnodejs.Node('electron_node'); + // node = new rclnodejs.Node('electron_node'); //-------------------------------------------------------------------------- // Create publishers - const message_string = 'std_msgs/msg/String'; - const message_int= 'std_msgs/msg/Int32'; + // const message_string = "std_msgs/msg/String"; + // const message_int = "std_msgs/msg/Int32"; - const topic_light = 'electron_light'; - publisher_light = node.createPublisher(message_string, topic_light); + // const topic_light = "electron_light"; + // publisher_light = node.createPublisher(message_string, topic_light); - const topic_brake = 'electron_brake'; - publisher_brake = node.createPublisher(message_string, topic_brake); + // const topic_brake = "electron_brake"; + // publisher_brake = node.createPublisher(message_string, topic_brake); - const topic_lidar = 'electron_lidar'; - publisher_lidar = node.createPublisher(message_string, topic_lidar); + // const topic_lidar = "electron_lidar"; + // publisher_lidar = node.createPublisher(message_string, topic_lidar); - const topic_selfdrive = 'electron_selfdrive'; - publisher_selfdrive = node.createPublisher(message_int, topic_selfdrive); + // const topic_selfdrive = "electron_selfdrive"; + // publisher_selfdrive = node.createPublisher(message_int, topic_selfdrive); - const topic_room = 'electron_room'; - publisher_room = node.createPublisher(message_int, topic_room); + // const topic_room = "electron_room"; + // publisher_room = node.createPublisher(message_int, topic_room); //-------------------------------------------------------------------------- // Create subscribers - const topic_battery = 'electron_battery'; - node.createSubscription(message_string, topic_battery, (msg) => { - console.log(`Received from Jetson: ${msg.data}`); - - temp = "BATTERY_" + msg.data; - if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { - mainWindow.webContents.send('update-display', temp); - } - }); - - const topic_speed = 'electron_speed'; - node.createSubscription(message_string, topic_speed, (msg) => { - console.log(`Received from Jetson: ${msg.data}`); - - temp = "SPEED_" + msg.data; - if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { - mainWindow.webContents.send('update-display', temp); - } - }); - - node.spin(); - console.log(`Created all publishers and subscribers. Waiting...`); - - defaultPublish(); - console.log(`Published default values. Waiting...`); + // const topic_battery = "electron_battery"; + // node.createSubscription(message_string, topic_battery, (msg) => { + // console.log(`Received from Jetson: ${msg.data}`); + // temp = "BATTERY_" + msg.data; + // if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { + // mainWindow.webContents.send("update-display", temp); + // } + // }); + + // const topic_speed = "electron_speed"; + // node.createSubscription(message_string, topic_speed, (msg) => { + // console.log(`Received from Jetson: ${msg.data}`); + // temp = "SPEED_" + msg.data; + // if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents) { + // mainWindow.webContents.send("update-display", temp); + // } + // }); + + // node.spin(); + // console.log(`Created all publishers and subscribers. Waiting...`); + + // defaultPublish(); + // console.log(`Published default values. Waiting...`); } //-------------------------------------------------------------------------------------------------- function defaultPublish() { + if (!publisher_light || !publisher_brake || !publisher_lidar || !publisher_selfdrive) { + console.log('Publishers not initialized, skipping defaultPublish'); + // Send default status to UI even without ROS + sendStatusToUI(); + return; + } + let temp = "LIGHT_OFF"; publisher_light.publish({ data: temp }); console.log(`---> Published from Electron app: ${temp}`); @@ -81,6 +96,18 @@ function defaultPublish() { temp = 0; publisher_selfdrive.publish({ data: temp }); console.log(`---> Published from Electron app: ${temp}`); + + sendStatusToUI(); +} + +function sendStatusToUI() { + if (mainWindow && !mainWindow.isDestroyed()) { + // Send individual status updates + mainWindow.webContents.send("update-display", `LIGHT_${systemStatus.light}`); + mainWindow.webContents.send("update-display", `BRAKE_${systemStatus.brake}`); + mainWindow.webContents.send("update-display", `LIDAR_${systemStatus.lidar}`); + mainWindow.webContents.send("update-display", `SELFDRIVE_${systemStatus.selfdrive}`); + } } //-------------------------------------------------------------------------------------------------- @@ -90,53 +117,33 @@ let lockWindow; let isShuttingDown = false; let actualShutdown = true; -function createLockWindow() { - lockWindow = new BrowserWindow({ +function createWindow() { + mainWindow = new BrowserWindow({ width: 970, height: 750, autoHideMenuBar: true, webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, "preload.js"), contextIsolation: true, - nodeIntegration: false - } + nodeIntegration: false, + }, }); - lockWindow.loadFile(path.join(__dirname, 'lock_screen.html')); - lockWindow.maximize(); - - lockWindow.on('close', (event) => { - if (!actualShutdown) { - return; + const isDev = process.env.NODE_ENV === 'development'; + if (isDev) { + mainWindow.loadURL('http://localhost:8080'); + } else { + mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } - - shutdownApp(); -}); -} - -function createMainWindow() { - mainWindow = new BrowserWindow({ // Create the window variable using BrowserWindow - width: 970, - height: 750, - frame: true, - autoHideMenuBar: true, // Hide menu bar - webPreferences: { - preload: path.join(__dirname, 'preload.js'), // __dirname gives the absolute path - contextIsolation: true, // Stuff to use with preload.js separation - nodeIntegration: false - } - }); - - mainWindow.loadFile(path.join(__dirname, 'index.html')); mainWindow.maximize(); - mainWindow.on('close', (event) => { - if (!actualShutdown) { - return; - } + mainWindow.on("close", (event) => { + if (!actualShutdown) { + return; + } - shutdownApp(); -}); + shutdownApp(); + }); } //-------------------------------------------------------------------- @@ -166,79 +173,107 @@ async function shutdownApp() { //-------------------------------------------------------------------------------------------------- // Interaction with render process -ipcMain.on('button-action', (event, action) => { +ipcMain.on("button-action", (event, action) => { console.log(`Received button action: ${action}`); // Lock Screen - if(action == "LOCK") { - actualShutdown = false; - mainWindow.close(); - actualShutdown = true; - createLockWindow(); + if (action == "LOCK") { + // Send lock message to React app + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("update-display", "LOCK"); + } defaultPublish(); } // Publish to ROS 2 & send display updates - if(action == "LIGHT_FLASHING" || action == "LIGHT_STEADY" || action == "LIGHT_OFF") { - publisher_light.publish({ data: action }); - console.log(`---> Published from Electron app: ${action}`); + if ( + action == "LIGHT_FLASHING" || + action == "LIGHT_STEADY" || + action == "LIGHT_OFF" + ) { + systemStatus.light = action.replace('LIGHT_', ''); + if (publisher_light) { + publisher_light.publish({ data: action }); + console.log(`---> Published from Electron app: ${action}`); + } } - if(action == "BRAKE_ON" || action == "BRAKE_OFF") { - publisher_brake.publish({ data: action }); - console.log(`---> Published from Electron app: ${action}`); + if (action == "BRAKE_ON" || action == "BRAKE_OFF") { + systemStatus.brake = action.replace('BRAKE_', ''); + if (publisher_brake) { + publisher_brake.publish({ data: action }); + console.log(`---> Published from Electron app: ${action}`); + } } if (action == "LIDAR_ON" || action == "LIDAR_OFF") { - publisher_lidar.publish({ data: action }); - console.log(`---> Published from Electron app: ${action}`); + systemStatus.lidar = action.replace('LIDAR_', ''); + if (publisher_lidar) { + publisher_lidar.publish({ data: action }); + console.log(`---> Published from Electron app: ${action}`); + } } //------------------------------------------------------------------------- // Self-drive Actions if (action == "SELFDRIVE_FULL") { // Self-drive full + systemStatus.selfdrive = 'FULL'; const msg = { data: 2 }; - publisher_selfdrive.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); - } - else if (action == "SELFDRIVE_JOYSTICK") { + if (publisher_selfdrive) { + publisher_selfdrive.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } + } else if (action == "SELFDRIVE_JOYSTICK") { // Self-drive w/Joystick + systemStatus.selfdrive = 'JOYSTICK'; const msg = { data: 1 }; - publisher_selfdrive.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); - } - else if (action == "SELFDRIVE_OFF") { + if (publisher_selfdrive) { + publisher_selfdrive.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } + } else if (action == "SELFDRIVE_OFF") { // Self-drive off + systemStatus.selfdrive = 'OFF'; const msg = { data: 0 }; - publisher_selfdrive.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); + if (publisher_selfdrive) { + publisher_selfdrive.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } } //------------------------------------------------------------------------- // Room destination actions - if(action.startsWith('ROOM_')) { - temp = action.substring(action.indexOf('_') + 1); + if (action.startsWith("ROOM_")) { + temp = action.substring(action.indexOf("_") + 1); const msg = { data: temp }; - publisher_room.publish(msg); - console.log(`---> Published from Electron app: ${msg.data}`); + if (publisher_room) { + publisher_room.publish(msg); + console.log(`---> Published from Electron app: ${msg.data}`); + } } //------------------------------------------------------------------------- // Send action name for screen update - mainWindow.webContents.send('update-display', action); + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("update-display", action); + } }); //------------------------------------------------------------ // Switching windows // Unlock -ipcMain.on('send-password', (event, password) => { +ipcMain.on("send-password", (event, password) => { const ACTUAL_PASSWORD = 7034; if (password == ACTUAL_PASSWORD) { - actualShutdown = false; - lockWindow.close(); - actualShutdown = true; - createMainWindow(); + // Send navigation message to React app + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("update-display", "NAVIGATE_TO_MAIN"); + // Send initial status after navigation + setTimeout(() => { + sendStatusToUI(); + }, 100); + } defaultPublish(); } }); @@ -246,16 +281,14 @@ ipcMain.on('send-password', (event, password) => { //-------------------------------------------------------------------------------------------------- // Other necessary stuff app.whenReady().then(async () => { - - createLockWindow(); + createWindow(); initROS(); - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) createWindows(); + app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); - }); -app.on('window-all-closed', function () { - if (process.platform !== 'darwin') app.quit(); +app.on("window-all-closed", function () { + if (process.platform !== "darwin") app.quit(); }); diff --git a/test-app/src/preload.js b/test-app/src/preload.js index f902351..3e77be9 100644 --- a/test-app/src/preload.js +++ b/test-app/src/preload.js @@ -1,14 +1,14 @@ -// Use contextBrindge to safely expose an API to the render process +// Use contextBridge to safely expose an API to the render process const { contextBridge, ipcRenderer } = require('electron/renderer'); -// Custom API named "electronAppAPI" +// Custom API named "electronAPI" // This allows the preload script to be loaded into the main process (main.js), -// while using the custom API in the render processes (renderer.js) -contextBridge.exposeInMainWorld('electronAppAPI', { +// while using the custom API in the render processes (React app) +contextBridge.exposeInMainWorld('electronAPI', { // General purpose button - sendAction: (action) => ipcRenderer.send('button-action', action), + sendButtonAction: (action) => ipcRenderer.send('button-action', action), - onDisplayUpdate: (callback) => ipcRenderer.on('update-display', (_, msg) => callback(msg)), + onUpdateDisplay: (callback) => ipcRenderer.on('update-display', (_, msg) => callback(msg)), sendPassword: (password) => ipcRenderer.send('send-password', password) }); \ No newline at end of file diff --git a/test-app/src/renderer/App.jsx b/test-app/src/renderer/App.jsx new file mode 100644 index 0000000..0019983 --- /dev/null +++ b/test-app/src/renderer/App.jsx @@ -0,0 +1,37 @@ +import React, { useState, useEffect } from 'react'; +import MainScreen from './components/MainScreen'; +import LockScreen from './components/LockScreen'; + +function App() { + const [isUnlocked, setIsUnlocked] = useState(false); + + useEffect(() => { + if (window.electronAPI) { + // Listen for navigation events from main process + window.electronAPI.onUpdateDisplay((message) => { + // Check if we should navigate to main screen + if (message === 'NAVIGATE_TO_MAIN') { + setIsUnlocked(true); + } else if (message === 'LOCK') { + setIsUnlocked(false); + } + }); + } + }, []); + + return ( +
+ {isUnlocked ? : } +
+ ); +} + +export default App; \ No newline at end of file diff --git a/test-app/src/renderer/components/LockScreen.jsx b/test-app/src/renderer/components/LockScreen.jsx new file mode 100644 index 0000000..ad813c5 --- /dev/null +++ b/test-app/src/renderer/components/LockScreen.jsx @@ -0,0 +1,248 @@ +import React, { useState } from "react"; + +function LockScreen() { + const [pin, setPin] = useState(""); + const [isShaking, setIsShaking] = useState(false); + + const addDigit = (digit) => { + if (pin.length < 4) { + setPin(pin + digit); + } + }; + + const removeDigit = () => { + if (pin.length > 0) { + setPin(pin.slice(0, -1)); + } + }; + + const submitPin = () => { + if (pin.length === 4) { + if (window.electronAPI) { + window.electronAPI.sendPassword(parseInt(pin)); + } + clearPin(); + } else if (pin.length > 0) { + // Add shake animation for incomplete PIN + setIsShaking(true); + setTimeout(() => setIsShaking(false), 500); + } + }; + + const clearPin = () => { + setPin(""); + }; + + const TouchButton = ({ onClick, style, children, variant = "default" }) => { + const [isPressed, setIsPressed] = useState(false); + + const baseStyle = { + width: "85px", + height: "85px", + borderRadius: "20px", + border: "none", + fontSize: "28px", + fontWeight: "600", + cursor: "pointer", + display: "flex", + alignItems: "center", + justifyContent: "center", + transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)", + userSelect: "none", + touchAction: "manipulation", + boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)", + ...style, + }; + + const variants = { + default: { + backgroundColor: isPressed ? "#e5e7eb" : "#ffffff", + color: "#1f2937", + border: "2px solid #f3f4f6", + }, + action: { + backgroundColor: isPressed ? "#3b82f6" : "#4f46e5", + color: "white", + }, + danger: { + backgroundColor: isPressed ? "#dc2626" : "#ef4444", + color: "white", + }, + }; + + return ( + + ); + }; + + return ( +
+ {/* Main Container */} +
+ {/* Header */} +
+
+ 🔒 +
+

+ Wheelchair Control +

+

+ Enter your PIN to continue +

+
+ + {/* PIN Display */} +
+ {Array.from({ length: 4 }, (_, i) => ( +
+ ))} +
+ + {/* Numpad */} +
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => ( + addDigit(num.toString())} + variant="default" + > + {num} + + ))} + + {/* Clear/Backspace */} + + ⌫ + + + {/* Zero */} + addDigit("0")} variant="default"> + 0 + + + {/* Submit */} + + ✓ + +
+ +
+ + +
+ ); +} + +export default LockScreen; diff --git a/test-app/src/renderer/components/MainScreen.jsx b/test-app/src/renderer/components/MainScreen.jsx new file mode 100644 index 0000000..35b7533 --- /dev/null +++ b/test-app/src/renderer/components/MainScreen.jsx @@ -0,0 +1,363 @@ +import React, { useState, useEffect } from 'react'; + +function MainScreen() { + const [display, setDisplay] = useState('System Ready'); + const [status, setStatus] = useState({ + light: 'OFF', + brake: 'ON', + lidar: 'OFF', + selfdrive: 'OFF', + battery: '---', + speed: '---', + room: '---' + }); + + useEffect(() => { + if (window.electronAPI) { + window.electronAPI.onUpdateDisplay((message) => { + setDisplay(message); + + // Update individual statuses based on the message + if (message.startsWith('LIGHT_')) { + setStatus(prev => ({ ...prev, light: message.replace('LIGHT_', '') })); + } else if (message.startsWith('BRAKE_')) { + setStatus(prev => ({ ...prev, brake: message.replace('BRAKE_', '') })); + } else if (message.startsWith('LIDAR_')) { + setStatus(prev => ({ ...prev, lidar: message.replace('LIDAR_', '') })); + } else if (message.startsWith('SELFDRIVE_')) { + setStatus(prev => ({ ...prev, selfdrive: message.replace('SELFDRIVE_', '') })); + } else if (message.startsWith('BATTERY_')) { + setStatus(prev => ({ ...prev, battery: message.replace('BATTERY_', '') })); + } else if (message.startsWith('SPEED_')) { + setStatus(prev => ({ ...prev, speed: message.replace('SPEED_', '') })); + } else if (message.startsWith('ROOM_')) { + setStatus(prev => ({ ...prev, room: message.replace('ROOM_', '') })); + } + }); + } + }, []); + + const handleButtonClick = (action) => { + if (window.electronAPI) { + window.electronAPI.sendButtonAction(action); + } + }; + + const buttonStyle = { + padding: '20px', + margin: '8px 0', + border: 'none', + borderRadius: '12px', + cursor: 'pointer', + fontSize: '18px', + fontWeight: '600', + color: 'white', + width: '100%', + minHeight: '60px', + userSelect: 'none', + touchAction: 'manipulation', + }; + + const statusGridStyle = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', + gap: '12px', + marginBottom: '24px' + }; + + const statusItemStyle = { + backgroundColor: 'white', + padding: '16px', + borderRadius: '8px', + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', + textAlign: 'center' + }; + + const statusLabelStyle = { + fontSize: '14px', + fontWeight: '600', + color: '#6b7280', + marginBottom: '8px' + }; + + const statusValueStyle = { + fontSize: '18px', + fontWeight: 'bold', + color: '#1f2937' + }; + + const getStatusColor = (status) => { + if (status === 'ON' || status === 'STEADY' || status === 'FLASHING' || status === 'FULL' || status === 'JOYSTICK') { + return '#22c55e'; + } + return '#6b7280'; + }; + + const TouchButton = ({ onClick, style, children, activeColor }) => { + const [isPressed, setIsPressed] = useState(false); + + return ( + + ); + }; + + return ( +
+ + {/* Status Grid */} +
+
+
Battery
+
{status.battery}
+
+
+
Speed
+
{status.speed}
+
+
+
Brake
+
{status.brake}
+
+
+
LiDAR
+
{status.lidar}
+
+
+
Self-Drive
+
{status.selfdrive}
+
+
+
Destination
+
{status.room}
+
+
+
Light
+
{status.light}
+
+
+ + {/* Control Grid */} +
+ + {/* Light Controls */} +
+

+ Lights +

+ handleButtonClick('LIGHT_STEADY')} + style={{ ...buttonStyle, backgroundColor: '#eab308' }} + > + Steady + + handleButtonClick('LIGHT_FLASHING')} + style={{ ...buttonStyle, backgroundColor: '#f97316' }} + > + Flashing + + handleButtonClick('LIGHT_OFF')} + style={{ ...buttonStyle, backgroundColor: '#6b7280' }} + > + Off + +
+ + {/* Brake Controls */} +
+

+ Brake +

+ handleButtonClick('BRAKE_ON')} + style={{ ...buttonStyle, backgroundColor: '#dc2626' }} + > + Brake On + + handleButtonClick('BRAKE_OFF')} + style={{ ...buttonStyle, backgroundColor: '#16a34a' }} + > + Brake Off + +
+ + {/* LIDAR Controls */} +
+

+ LiDAR +

+ handleButtonClick('LIDAR_ON')} + style={{ ...buttonStyle, backgroundColor: '#2563eb' }} + > + LiDAR On + + handleButtonClick('LIDAR_OFF')} + style={{ ...buttonStyle, backgroundColor: '#6b7280' }} + > + LiDAR Off + +
+ + {/* Self-Drive Controls */} +
+

+ Self-Drive +

+ handleButtonClick('SELFDRIVE_FULL')} + style={{ ...buttonStyle, backgroundColor: '#7c3aed' }} + > + Full Auto + + handleButtonClick('SELFDRIVE_JOYSTICK')} + style={{ ...buttonStyle, backgroundColor: '#4338ca' }} + > + With Joystick + + handleButtonClick('SELFDRIVE_OFF')} + style={{ ...buttonStyle, backgroundColor: '#6b7280' }} + > + Manual + +
+ + {/* Room Navigation */} +
+

+ Room Navigation +

+
+ {['400', '404', '410', '429', '423', '419', '426', '422', '416', '414'].map((room) => ( + handleButtonClick(`ROOM_${room}`)} + style={{ + ...buttonStyle, + backgroundColor: '#059669', + padding: '16px', + fontSize: '16px', + minHeight: '50px' + }} + > + {room} + + ))} +
+
+ + {/* System Controls */} +
+

+ System +

+ handleButtonClick('LOCK')} + style={{ ...buttonStyle, backgroundColor: '#dc2626', fontSize: '20px' }} + > + 🔒 Lock System + +
+
+
+ ); +} + +export default MainScreen; \ No newline at end of file diff --git a/test-app/src/renderer/index.css b/test-app/src/renderer/index.css new file mode 100644 index 0000000..2b0c195 --- /dev/null +++ b/test-app/src/renderer/index.css @@ -0,0 +1,70 @@ +/* Touch-screen optimized global styles */ +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + height: 100%; + overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; +} + +/* Disable all keyboard input globally */ +input, textarea, select { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +/* Allow pointer events only for our custom buttons */ +button { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + outline: none; +} + +/* Prevent text selection and context menus */ +*:not(input):not(textarea) { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-touch-callout: none; +} + +/* Disable zoom on mobile */ +html { + -ms-touch-action: manipulation; + touch-action: manipulation; +} + +/* Disable browser default behaviors */ +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; + overflow-y: hidden; +} + +/* Hide scrollbars */ +::-webkit-scrollbar { + display: none; +} + +/* Ensure full screen app */ +#root { + height: 100vh; + width: 100vw; + overflow: hidden; +} \ No newline at end of file diff --git a/test-app/src/renderer/index.html b/test-app/src/renderer/index.html new file mode 100644 index 0000000..f84da2c --- /dev/null +++ b/test-app/src/renderer/index.html @@ -0,0 +1,20 @@ + + + + + + Wheelchair Control System + + + +
+ + \ No newline at end of file diff --git a/test-app/src/renderer/index.jsx b/test-app/src/renderer/index.jsx new file mode 100644 index 0000000..fb6e159 --- /dev/null +++ b/test-app/src/renderer/index.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App'; +import './index.css'; + +const container = document.getElementById('root'); +const root = createRoot(container); +root.render(); \ No newline at end of file diff --git a/test-app/src/style.css b/test-app/src/style.css index 21bd729..eea7ab7 100644 --- a/test-app/src/style.css +++ b/test-app/src/style.css @@ -199,7 +199,6 @@ body { justify-content: center; overflow: hidden; - transition: transform 0.1s ease; border: 2.5px solid #ffffff; } @@ -218,7 +217,6 @@ button[data-action="BRAKE_ON"] { background: #e60000; width: 100%; height: 100%; button[data-action="BRAKE_OFF"] { background: #e38585; width: 100%; height: 100%; border-radius: 25px;} .app-button.pressed { - transform: scale(1.05); opacity: 0.8; /* border: 3px solid #ffffff; */ } diff --git a/test-app/webpack.config.js b/test-app/webpack.config.js new file mode 100644 index 0000000..ebc9ab5 --- /dev/null +++ b/test-app/webpack.config.js @@ -0,0 +1,41 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); + +module.exports = { + mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', + entry: './src/renderer/index.jsx', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'renderer.js', + clean: true, + }, + target: 'electron-renderer', + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', '@babel/preset-react'], + }, + }, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader', 'postcss-loader'], + }, + ], + }, + resolve: { + extensions: ['.js', '.jsx'], + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './src/renderer/index.html', + filename: 'index.html', + }), + ], + devtool: process.env.NODE_ENV === 'production' ? false : 'eval-source-map', +}; \ No newline at end of file