From 4bc729b29c76a53ec3c270ba0f3953d50d273077 Mon Sep 17 00:00:00 2001 From: Ahmed Mahfouz Date: Mon, 2 Sep 2024 12:19:44 +0200 Subject: [PATCH] feat add features styles with css and not js The previous impelemtation had each features styles added via js. These were lots of styles to add in each feature. I opted for another solution which is with css, which is what css is used for. It will make it easier to make the styles in js for the index page later. For now, the styles are working fine and I will polish some touches in it --- teleop/htm/static/.DS_Store | Bin 6148 -> 6148 bytes teleop/htm/static/CSS/menu_settings.css | 1 + teleop/htm/static/CSS/mobileController.css | 38 ++- teleop/htm/static/CSS/theme_mode.css | 277 +++++++++++++++++- teleop/htm/static/CSS/user_menu.css | 8 +- teleop/htm/static/JS/Index/index_c_screen.js | 39 +-- .../mobileController_f_auto_navigation.js | 63 ++++ .../feature/mobileController_f_confidence.js | 105 +++++++ .../feature/mobileController_f_following.js | 166 +++++++++++ .../mobileController_f_maneuver_training.js | 49 ++++ .../mobileController_c_logic.js | 1 - .../mobileController_f_Inference.js | 226 -------------- .../mobileController_f_auto_navigation.js | 70 ----- .../mobileController_f_confidence.js | 159 ---------- .../mobileController_f_following.js | 190 ------------ .../mobileController_f_maneuver_training.js | 54 ---- teleop/htm/static/JS/router.js | 35 ++- teleop/htm/templates/index.html | 5 +- .../htm/templates/mobile_controller_ui.html | 10 +- teleop/htm/templates/normal_ui.html | 4 +- 20 files changed, 735 insertions(+), 765 deletions(-) create mode 100644 teleop/htm/static/JS/mobileController/feature/mobileController_f_auto_navigation.js create mode 100644 teleop/htm/static/JS/mobileController/feature/mobileController_f_confidence.js create mode 100644 teleop/htm/static/JS/mobileController/feature/mobileController_f_following.js create mode 100644 teleop/htm/static/JS/mobileController/feature/mobileController_f_maneuver_training.js delete mode 100644 teleop/htm/static/JS/mobileController/mobileController_f_Inference.js delete mode 100644 teleop/htm/static/JS/mobileController/mobileController_f_auto_navigation.js delete mode 100644 teleop/htm/static/JS/mobileController/mobileController_f_confidence.js delete mode 100644 teleop/htm/static/JS/mobileController/mobileController_f_following.js delete mode 100644 teleop/htm/static/JS/mobileController/mobileController_f_maneuver_training.js diff --git a/teleop/htm/static/.DS_Store b/teleop/htm/static/.DS_Store index 4ec085fa1afd8ee0537441f033cbdfd106186509..232a77ce65ebe10c42c4b27b086d3df46df2957a 100644 GIT binary patch delta 64 zcmZoMXfc@JFU-uqz`)4BAiz+Rmy@5D4&qJz$hcA*B+ks>%n-~FTo#Ow+swoGlx;IR H$6tN`#di+} delta 49 zcmZoMXfc@JFUrKgz`)4BAi$86lb-}+??@`h$($_6w0yFG2+L+w#z$-u8^kuVbNuB8 E04AOdOaK4? diff --git a/teleop/htm/static/CSS/menu_settings.css b/teleop/htm/static/CSS/menu_settings.css index 074d9b15..26f64e78 100644 --- a/teleop/htm/static/CSS/menu_settings.css +++ b/teleop/htm/static/CSS/menu_settings.css @@ -69,6 +69,7 @@ fieldset input[type=text] { #pro-view-toggle-div { position: relative; width: fit-content; + padding-top: 3vh; } #pro-view-toggle-div span { diff --git a/teleop/htm/static/CSS/mobileController.css b/teleop/htm/static/CSS/mobileController.css index d0e4d760..ccf960ea 100644 --- a/teleop/htm/static/CSS/mobileController.css +++ b/teleop/htm/static/CSS/mobileController.css @@ -20,25 +20,26 @@ border-bottom: 1px solid var(--VOR-color); } -#mobile_controller_container #horizontal_bar .left_section, -#mobile_controller_container #horizontal_bar .middle_section, -#mobile_controller_container #horizontal_bar .right_section { +#mobile_controller_container .left_section, +#mobile_controller_container .middle_section, +#mobile_controller_container .right_section { display: flex; align-items: center; - flex: 1; + } -#mobile_controller_container #horizontal_bar .left_section { +#mobile_controller_container .left_section { justify-content: flex-start; flex: 2; padding-left: 13px; } -#mobile_controller_container #horizontal_bar .middle_section { +#mobile_controller_container .middle_section { justify-content: center; + flex: 0; } -#mobile_controller_container #horizontal_bar .right_section { +#mobile_controller_container .right_section { flex-direction: row; align-content: flex-end; flex: 2; @@ -47,17 +48,22 @@ #horizontal_bar .current_mode_text { text-transform: uppercase; + display: block; +} + +#horizontal_bar .current_mode_text:before { + content: "manual drive" } .current_mode_button { outline: none; box-shadow: 4px 6px 8px #d0d0d0; display: none; - width: 100%; + width: 50%; transition: background-color 0.3s, transform 0.2s; cursor: pointer; border: 2px solid; - border-radius: 20px; + border-radius: 8px; font-weight: bold; color: #451c58; text-transform: uppercase; @@ -85,20 +91,22 @@ border-radius: 1rem; } -#backward_square.maneuver_square { - width: 100%; - background: repeating-linear-gradient(-45deg, #ffffff, #ffffff 16px, #ccc 18px, #ccc 11px); - border: 0; -} - #forward_square .square_text { bottom: 2vh; } +#forward_square .square_text:before { + content: "forward" +} + #backward_square .square_text { top: 2vh; } +#backward_square .square_text:before { + content:"backward" +} + .square_text { pointer-events: none; display: block; diff --git a/teleop/htm/static/CSS/theme_mode.css b/teleop/htm/static/CSS/theme_mode.css index 68298018..6d6d0e56 100644 --- a/teleop/htm/static/CSS/theme_mode.css +++ b/teleop/htm/static/CSS/theme_mode.css @@ -1,22 +1,285 @@ -body.dark-mode { +/* Advanced mode */ +body.advanced-theme .advanced_view { + display: none !important; +} + + +/* #region Dark mode */ +body.dark-theme { background-color: var(--background_color_dark_mode); color: var(--text_color_dark_mode); } -body.dark-mode .caret { +body.dark-theme .caret { color: var(--text_color_dark_mode); } -body.dark-mode #hamburger_menu_toggle .bar { +body.dark-theme #hamburger_menu_toggle .bar { background-color: var(--background_color_light_mode); } -body.dark-mode #video_stream_type { +body.dark-theme #video_stream_type { background-color: var(--background_color_light_mode); color: var(--text_color_light_mode); } +/* #endregion */ + +/* #region AI training feature */ +body.maneuver-training-feature .steeringWheel { + display: none; +} + +body.maneuver-training-feature #horizontal_bar .current_mode_text:before { + content: "ai training" +} + +body.maneuver-training-feature #mobile_controller_container #backward_square .trail_canvas, +body.maneuver-training-feature #mobile_controller_container #backward_square .square_text { + display: none; +} + +body.maneuver-training-feature #mobile_controller_container #forward_square .square_text:before { + content: 'forward'; +} + +body.maneuver-training-feature #mobile_controller_container #backward_square { + display: block; + width: 100%; + background: repeating-linear-gradient(-45deg, var(--background_color_light_mode), var(--background_color_light_mode) 16px, #ccc 18px, #ccc 11px); + border: 0; +} + +body.maneuver-training-feature #mobile_controller_container .control_symbol { + display: none; +} + +body.maneuver-training-feature .current_mode_button { + display: block; + background-color: #451c58; + color: white; + box-shadow: none; +} + +body.maneuver-training-feature .current_mode_button:before { + content: "start" +} + +/* Training started state */ +body.maneuver-training-feature.training-started .current_mode_button { + background-color: #f41e52; + border: none; + content: 'stop'; +} + +body.maneuver-training-feature.training-started .current_mode_button:before { + content: "stop"; +} + +/* #endregion */ + +/* #region Auto navigation feature */ +body.auto-navigation-feature .steeringWheel { + display: none; +} + +body.auto-navigation-feature #horizontal_bar .current_mode_text:before { + content: "auto navigation" +} + +body.auto-navigation-feature .current_mode_button:before { + content: 'start'; +} + +body.auto-navigation-feature #forward_square .square_text:before { + content: "Increase max speed" +} + +body.auto-navigation-feature #backward_square .square_text:before { + content: "decrease max speed" +} + + + +body.auto-navigation-feature .current_mode_button { + display: block; + background-color: #451c58; + color: white; + box-shadow: none; +} + +body.auto-navigation-feature .current_mode_text, +body.auto-navigation-feature #mobile_controller_container .trail_canvas { + display: none; +} + +body.auto-navigation-feature .rover_speed, +body.auto-navigation-feature .control_symbol { + display: flex; +} + + +body.auto-navigation-feature.navigation-started .current_mode_button { + background-color: #f41e52; + border: none; + content: 'stop'; +} + +body.auto-navigation-feature.navigation-started .current_mode_button:before { + content: 'stop'; +} + +/* #endregion */ + +/* #region Confidence feature */ +body.confidence-feature .current_mode_text { + display: block; +} + +body.confidence-feature #horizontal_bar .current_mode_text:before { + content: "map recognize" +} + +body.confidence-feature .steeringWheel { + display: none; +} + +body.confidence-feature .current_mode_button { + display: block; + background-color: #451c58; + color: white; + box-shadow: none; +} + +/* Recognize mode */ +body.confidence-feature.recognize-mode .current_mode_button:before { + content: 'start'; +} + +body.confidence-feature.recognize-mode .control_symbol, +body.confidence-feature.recognize-mode .stop_text { + display: none; +} + +/* Stop mode */ +body.confidence-feature.stop-mode .current_mode_button { + background-color: #f41e52; + border: none; +} + +body.confidence-feature.stop-mode .current_mode_button:before { + content: 'stop'; +} + +body.confidence-feature.stop-mode #mobile_controller_container .square { + display: block; +} + +body.confidence-feature.stop-mode .stop_text, +body.confidence-feature.stop-mode .control_symbol { + display: none; +} + +/* Return mode */ +body.confidence-feature.return-mode .current_mode_button { + background-color: #ffffff; + color: #451c58; +} + +body.confidence-feature.return-mode .current_mode_button:before { + content: 'return'; +} + +/* Show result mode */ +body.confidence-feature.show-result-mode .current_mode_button { + display: block; + border: none; + content: 'Show result'; +} + +body.confidence-feature.show-result-mode .current_mode_button:before { + content: 'Show result'; +} + +/* Loading mode */ +body.confidence-feature.loading-mode .current_mode_state { + display: block; + color: #ff8a00; +} + +body.confidence-feature.loading-mode .current_mode_state:before { + content: 'Loading...'; +} + +body.confidence-feature.loading-mode .current_mode_button { + display: none; +} + +/* #endregion */ + +/* #region Following feature */ +body.following-feature .current_mode_button { + display: block; +} + +body.following-feature#horizontal_bar .current_mode_text:before { + content: "follow" +} + +body.following-feature #mobile_controller_container .middle_section, +body.following-feature #mobile_controller_container .square { + display: none; +} + +/* Image mode */ +body.following-feature.image-mode .current_mode_button { + background-color: #ffffff; + border: none; +} + +body.following-feature.image-mode .current_mode_button::before { + content: 'start '; +} + +body.following-feature.image-mode #mobile_controller_container .current_mode_state, +body.following-feature.image-mode #mobile_controller_container .square { + display: none; +} + +/* Active mode */ +body.following-feature.active-mode .current_mode_button { + background-color: #f41e52; + border: none; +} + +body.following-feature.active-mode .current_mode_button::before { + content: 'stop '; +} + +body.following-feature.active-mode #mobile_controller_container .current_mode_state, +body.following-feature.active-mode #mobile_controller_container .square { + display: none; +} + +/* Inactive mode */ +body.following-feature.inactive-mode .current_mode_button { + background-color: #ffffff; + border: none; + content: 'start'; +} + +body.following-feature.inactive-mode #mobile_controller_container .square { + display: block; +} + +/* Loading mode */ +body.following-feature.loading-mode #mobile_controller_container .current_mode_state:before { + content: 'Loading...'; +} + +body.following-feature.loading-mode .current_mode_button, +body.following-feature.loading-mode #mobile_controller_container .square { + display: none; +} -body.advanced-mode .advanced_view { - display: none !important ; -} \ No newline at end of file +/* #endregion */ \ No newline at end of file diff --git a/teleop/htm/static/CSS/user_menu.css b/teleop/htm/static/CSS/user_menu.css index 7ead4b02..714fcbb3 100644 --- a/teleop/htm/static/CSS/user_menu.css +++ b/teleop/htm/static/CSS/user_menu.css @@ -132,7 +132,7 @@ display: flex; align-items: center; text-decoration: none; - padding: 1rem 0; + margin: 1rem 0; transition: font-size 0.3s ease, color 0.3s ease; } @@ -169,6 +169,12 @@ .hamburger_menu_nav.active { left: 0; } +#hamburger_menu_top_nav{ + display: flex; + justify-content: flex-end; + align-items: flex-start; + flex-direction: column; +} #hamburger_menu_bottom_nav { margin-top: auto; diff --git a/teleop/htm/static/JS/Index/index_c_screen.js b/teleop/htm/static/JS/Index/index_c_screen.js index c2f9f2bf..535e8562 100644 --- a/teleop/htm/static/JS/Index/index_c_screen.js +++ b/teleop/htm/static/JS/Index/index_c_screen.js @@ -1,6 +1,6 @@ import CTRL_STAT from '../mobileController/mobileController_z_state.js'; // Stands for control state -import { dev_tools, isMobileDevice, page_utils } from './index_a_utils.js'; +import { isMobileDevice, page_utils } from './index_a_utils.js'; import { gamepad_controller } from './index_b_gamepad.js'; import { gamepad_socket } from './index_e_teleop.js'; @@ -33,7 +33,7 @@ class DarkThemeManager { } setTheme(isDarkMode) { - this.body.classList.toggle('dark-mode', isDarkMode); //Add dark mode to body only + this.body.classList.toggle('dark-theme', isDarkMode); //Add dark mode to body only this.updateLogo(isDarkMode); } @@ -90,9 +90,9 @@ class AdvancedThemeManager { setAdvancedTheme() { if (this.isAdvancedMode) { - $('body').removeClass('advanced-mode'); + $('body').removeClass('advanced-theme'); } else { - $('body').addClass('advanced-mode'); + $('body').addClass('advanced-theme'); } } } @@ -279,7 +279,6 @@ class MessageContainerManager { } } - class PathRenderer { // Method to render a trapezoid shape _renderTrapezoid(ctx, positions, fill = 'rgba(100, 217, 255, 0.3)') { @@ -428,7 +427,6 @@ class RoverUI { overlayLeftMarker1: this.getElement('div#overlay_left_marker1'), overlayRightMarker0: this.getElement('div#overlay_right_marker0'), overlayRightMarker1: this.getElement('div#overlay_right_marker1'), - infSpeed: this.getElement('div.inf_speed'), autopilotOperatingTime: this.getElement('.inf_operating_time'), messageBoxMessage: this.getElement('div#message_box_message'), buttonTakeControl: this.getElement('input#message_box_button_take_control'), @@ -445,7 +443,6 @@ class RoverUI { this.overlayCenterMarkers = [elements.overlayCenterDistance0, elements.overlayCenterDistance1]; this.overlayLeftMarkers = [elements.overlayLeftMarker0, elements.overlayLeftMarker1]; this.overlayRightMarkers = [elements.overlayRightMarker0, elements.overlayRightMarker1]; - this.elInfSpeed = elements.infSpeed; this.elAutopilotOperatingTime = elements.autopilotOperatingTime; } catch (error) { console.error('Error in RoverUI.setNormalUIElements():', error); @@ -579,13 +576,13 @@ class InferenceHandling { } updateAutopilotUI(infMessage) { - this.roverUI.elInfSpeed.show(); - this.roverUI.elAutopilotOperatingTime.show(); + $('div.inf_speed').show(); + $('.inf_operating_time').show(); $('p.inf_speed_value').text(`${infMessage.max_speed.toFixed(1)} KM`); $('div.inf_speed_label').text('Max Speed'); this.roverUI.renderDistanceIndicators('front'); - this.controlCurrentModeButton(true); + // this.controlCurrentModeButton(true); if (infMessage.ctl_activation > 0) { this.updateAutopilotTimeDisplay(infMessage.ctl_activation); @@ -593,11 +590,9 @@ class InferenceHandling { } updateAutopilotTimeDisplay(ctlActivation) { - const elAutopilotOperatingTime = $('.inf_operating_time'); const time = this.formatTime(ctlActivation * 1e-3); // Convert ms to seconds - - elAutopilotOperatingTime.text(time); - elAutopilotOperatingTime.css('color', 'rgb(100, 217, 255)'); + $('.inf_operating_time').text(time); + $('.inf_operating_time').css('color', 'rgb(100, 217, 255)'); } formatTime(totalSeconds) { @@ -613,13 +608,11 @@ class InferenceHandling { } hideAutopilotUI() { - const elAutopilotOperatingTime = this.roverUI.elAutopilotOperatingTime; - - this.roverUI.elInfSpeed.hide(); - elAutopilotOperatingTime.text('00:00:00'); - elAutopilotOperatingTime.hide(); + $('div.inf_speed').hide(); + $('.inf_operating_time').text('00:00:00'); + $('.inf_operating_time').hide(); - this.controlCurrentModeButton(false); + // this.controlCurrentModeButton(false); } updateSteeringWheelRotation(speed, steeringAngle) { @@ -670,8 +663,8 @@ class CameraControls { // Add listeners to update RoverUI whenever the camera changes this.addCameraActivationListener((name) => { - this.roverUI.activeCamera = name; - this.roverUI.renderDistanceIndicators(name); + this.roverUI.activeCamera = name; + this.roverUI.renderDistanceIndicators(name); }); } selectNextCamera() { @@ -758,4 +751,4 @@ const roverUI = new RoverUI(); const inferenceHandling = new InferenceHandling(roverUI); const cameraControls = new CameraControls(roverUI); -export { roverUI, inferenceHandling, cameraControls, helpMessageManager, messageContainerManager, advancedThemeManager }; +export { advancedThemeManager, cameraControls, helpMessageManager, inferenceHandling, messageContainerManager, roverUI }; diff --git a/teleop/htm/static/JS/mobileController/feature/mobileController_f_auto_navigation.js b/teleop/htm/static/JS/mobileController/feature/mobileController_f_auto_navigation.js new file mode 100644 index 00000000..8e523daf --- /dev/null +++ b/teleop/htm/static/JS/mobileController/feature/mobileController_f_auto_navigation.js @@ -0,0 +1,63 @@ +import { addDataToMobileCommand } from '../mobileController_c_logic.js'; +import CTRL_STAT from '../mobileController_z_state.js'; + +class AutoNavigationHandler { + constructor() {} + + initializeDOM() { + // Add the class to enable auto navigation feature styles + $('body').addClass('auto-navigation-feature'); + this.updateNavigationState(); + + this.bindButtonAction(); + } + + bindButtonAction() { + $('#mobile_controller_container .current_mode_button').click((event) => { + const buttonText = $(event.target).text().toLowerCase(); + if (CTRL_STAT.currentPage === 'autopilot_link' && buttonText === 'start') this.startAutoNavigation(); + if (CTRL_STAT.currentPage === 'autopilot_link' && buttonText === 'stop') this.stopAutoNavigation(); + }); + + document.querySelectorAll('.control_symbol').forEach((item) => { + item.addEventListener('touchstart', (event) => { + item.classList.add('active'); + const command = item.textContent.trim() === '+' ? 'increase' : 'decrease'; + this.handleSpeedControl(command); + }); + item.addEventListener('touchend', () => { + item.classList.remove('active'); + }); + }); + } + + startAutoNavigation() { + addDataToMobileCommand({ button_y: 1 }); + $('body').addClass('navigation-started'); + } + + stopAutoNavigation() { + addDataToMobileCommand({ button_b: 1 }); + $('body').removeClass('navigation-started') + } + + updateNavigationState() { + // Set the initial state of the button and styles + if ($('body').hasClass('navigation-started')) { + this.startAutoNavigation(); + } else { + this.stopAutoNavigation(); + } + } + + handleSpeedControl(cmd) { + if (cmd === 'increase') { + addDataToMobileCommand({ arrow_up: 1 }); + } else if (cmd === 'decrease') { + addDataToMobileCommand({ arrow_down: 1 }); + } + } +} + +var autoNavigationNavButtonHandler = new AutoNavigationHandler(); +export { autoNavigationNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/feature/mobileController_f_confidence.js b/teleop/htm/static/JS/mobileController/feature/mobileController_f_confidence.js new file mode 100644 index 00000000..e5afad69 --- /dev/null +++ b/teleop/htm/static/JS/mobileController/feature/mobileController_f_confidence.js @@ -0,0 +1,105 @@ +import CTRL_STAT from '../mobileController_z_state.js'; + +class ConfidenceHandler { + constructor() { + this.confidenceWS = {}; + } + + initializeDOM() { + $('body').addClass('confidence-feature'); + this.toggleButtonAppearance('start'); + this.initializeConfidenceWS(); + this.bindButtonAction(); + } + + bindButtonAction() { + $('.current_mode_button').click((event) => { + const buttonText = $(event.target).text().toLowerCase(); + if (CTRL_STAT.currentPage == 'map_recognition_link' && buttonText == 'recognize') { + this.sendSwitchConfidenceRequest('start_confidence'); + this.toggleButtonAppearance('stop'); + } else if (CTRL_STAT.currentPage == 'map_recognition_link' && buttonText == 'stop') { + this.sendSwitchConfidenceRequest('stop_confidence'); + this.toggleButtonAppearance('start'); + } else if (CTRL_STAT.currentPage == 'map_recognition_link' && buttonText == 'return') { + $('#map_frame, #top_layer_iframe').hide(); + this.toggleButtonAppearance('start'); + } + }); + } + + sendSwitchConfidenceRequest(command) { + console.log(command); + if (this.confidenceWS.websocket && this.confidenceWS.websocket.readyState === WebSocket.OPEN) { + this.confidenceWS.websocket.send(command); + } else { + console.error('Confidence websocket is not open. Command not sent. Attempting to reconnect...'); + this.checkAndReconnectWebSocket(); + } + } + + toggleButtonAppearance(cmd) { + $('body').removeClass('recognize-mode stop-mode return-mode show-result-mode'); + + if (cmd == 'start') { + $('body').addClass('recognize-mode'); + } else if (cmd == 'stop') { + $('body').addClass('stop-mode'); + } else if (cmd == 'return') { + $('body').addClass('return-mode'); + } else if (cmd == 'show_result') { + $('body').addClass('show-result-mode'); + } + } + + initializeConfidenceWS() { + let WSprotocol = document.location.protocol === 'https:' ? 'wss://' : 'ws://'; + this.currentURL = `${document.location.protocol}`; + let WSurl = `${WSprotocol}${document.location.hostname}:${document.location.port}/ws/switch_confidence`; + this.confidenceWS.websocket = new WebSocket(WSurl); + + this.confidenceWS.websocket.onopen = () => { + this.confidenceWS.isWebSocketOpen = true; + }; + + this.confidenceWS.websocket.onmessage = (event) => { + this.updateButtonState(event.data); + }; + + this.confidenceWS.websocket.onclose = () => { + console.log('Confidence websocket connection closed'); + this.confidenceWS.isWebSocketOpen = false; + setTimeout(() => this.checkAndReconnectWebSocket(), 500); + }; + } + + checkAndReconnectWebSocket() { + if (!this.confidenceWS.websocket || this.confidenceWS.websocket.readyState === WebSocket.CLOSED) { + this.initializeConfidenceWS(); + } + } + + loadMapIntoIframe(url) { + const iframeSelector = '#map_frame, #top_layer_iframe'; + $(iframeSelector).attr('src', `${this.currentURL}/overview_confidence/${url}`).fadeIn(500); + } + + updateButtonState(message) { + if (message === 'loading') { + $('body').addClass('loading-mode').removeClass('show-result-mode'); + } else if (message.endsWith('.html')) { + const filename = message.match(/[\w-]+\.html$/)[0]; + this.toggleButtonAppearance('show_result'); + + $('.current_mode_button') + .off('click') + .click(() => { + this.loadMapIntoIframe(filename); + this.toggleButtonAppearance('return'); + }); + } + } +} + +var confidenceNavButtonHandler = new ConfidenceHandler(); +export { confidenceNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/feature/mobileController_f_following.js b/teleop/htm/static/JS/mobileController/feature/mobileController_f_following.js new file mode 100644 index 00000000..afa5e098 --- /dev/null +++ b/teleop/htm/static/JS/mobileController/feature/mobileController_f_following.js @@ -0,0 +1,166 @@ +import CTRL_STAT from '../mobileController_z_state.js'; + +class FollowingHandler { + constructor() { + this.errorLogged = false; + this.initialSetup(); + this.startPolling(); + } + + initializeDOM() { + $('body').addClass('following-feature'); + this.sendSwitchFollowingRequest('show_image'); + this.bindButtonAction(); + this.initializeCanvas(); + } + + bindButtonAction() { + $('#mobile_controller_container .current_mode_button').click(() => { + if (CTRL_STAT.followingState === 'inactive') { + this.sendSwitchFollowingRequest('show_image'); + } else if (CTRL_STAT.followingState === 'image') { + this.sendSwitchFollowingRequest('start_following'); + } else if (CTRL_STAT.followingState === 'active') { + this.sendSwitchFollowingRequest('show_image'); + } + }); + } + + initializeCanvas() { + this.canvas = document.getElementById('following_imageCanvas'); + if (this.canvas) { + this.ctx = this.canvas.getContext('2d'); + } else { + setTimeout(() => this.initializeCanvas(), 500); + } + } + + initialSetup() { + window.addEventListener('resize', () => this.resizeCanvas()); + } + + sendSwitchFollowingRequest(command) { + fetch('/fol_handler', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `command=${encodeURIComponent(command)}`, + }) + .then((response) => response.json()) + .catch((error) => console.error('Error sending command:', error)); + } + + startPolling() { + setInterval(() => { + if (CTRL_STAT.currentPage == 'follow_link') { + fetch('/fol_handler', { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }) + .then((response) => response.json()) + .then((data) => { + const previousState = CTRL_STAT.followingState; + this.assignFollowingState(data.following_status); + this.toggleButtonAppearance(); + if (previousState !== CTRL_STAT.followingState) { + // Additional actions can be added here if needed when state changes + } + this.errorLogged = false; + }) + .catch((error) => { + if (!this.errorLogged) { + console.error('Error polling backend:', error); + this.errorLogged = true; + } + }); + } + }, 500); + } + + assignFollowingState(backendCommand) { + switch (backendCommand) { + case 'active': + CTRL_STAT.followingState = 'active'; + break; + case 'image': + CTRL_STAT.followingState = 'image'; + break; + case 'inactive': + CTRL_STAT.followingState = 'inactive'; + break; + case 'loading': + CTRL_STAT.followingState = 'loading'; + break; + default: + console.log('Following: Unknown command received from the backend:', backendCommand); + } + } + + toggleButtonAppearance() { + $('body').removeClass('image-mode active-mode inactive-mode loading-mode'); + + if (CTRL_STAT.followingState === 'image') { + this.resizeCanvas(); + this.showCanvas(); + $('body').addClass('image-mode'); + } else if (CTRL_STAT.followingState === 'active') { + this.resizeCanvas(); + this.showCanvas(); + $('body').addClass('active-mode'); + } else if (CTRL_STAT.followingState === 'inactive') { + $('body').addClass('inactive-mode'); + this.hideCanvas(); + } else if (CTRL_STAT.followingState === 'loading') { + $('body').addClass('loading-mode'); + this.hideCanvas(); + } + } + + showCanvas() { + if (this.canvas) { + this.canvas.style.display = 'block'; + if (!this.streamActive && !this.intervalId) { + this.streamActive = true; + this.intervalId = setInterval(() => this.refreshImage(), 30); + } + } + } + + hideCanvas() { + if (this.canvas) { + this.canvas.style.display = 'none'; + if (this.streamActive && this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + this.streamActive = false; + } + } + } + + refreshImage() { + if (!this.streamActive) return; + + const img = new Image(); + img.onload = () => { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height); + }; + img.src = '/latest_image?' + new Date().getTime(); + } + + resizeCanvas() { + if (this.canvas) { + let maxWidth = window.innerWidth * 0.8; + if (maxWidth > 640) maxWidth = 640; + const maxHeight = (maxWidth * 3) / 4; + + this.canvas.width = maxWidth; + this.canvas.height = maxHeight; + this.canvas.style.width = `${maxWidth}px`; + this.canvas.style.height = `${maxHeight}px`; + } + } +} + +var followingNavButtonHandler = new FollowingHandler(); + +export { followingNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/feature/mobileController_f_maneuver_training.js b/teleop/htm/static/JS/mobileController/feature/mobileController_f_maneuver_training.js new file mode 100644 index 00000000..4ccdc14f --- /dev/null +++ b/teleop/htm/static/JS/mobileController/feature/mobileController_f_maneuver_training.js @@ -0,0 +1,49 @@ +import { addDataToMobileCommand } from '../mobileController_c_logic.js'; +import CTRL_STAT from '../mobileController_z_state.js'; + +class ManeuverTrainingHandler { + constructor() {} + + initializeDOM() { + // Add the class to enable maneuver training feature styles + $('body').addClass('maneuver-training-feature'); + // Ensure the correct state is reflected (start/stop) + this.updateTrainingState(); + + this.bindButtonAction(); + } + + bindButtonAction() { + $('#mobile_controller_container .F').click((event) => { + const buttonText = $(event.target).text().toLowerCase(); + if (CTRL_STAT.currentPage === 'ai_training_link' && buttonText === 'start') { + this.startTraining(); + } + if (CTRL_STAT.currentPage === 'ai_training_link' && buttonText === 'stop') { + this.stopTraining(); + } + }); + } + + startTraining() { + addDataToMobileCommand({ button_y: 1 }); + $('body').addClass('training-started'); + } + + stopTraining() { + addDataToMobileCommand({ button_b: 1 }); + $('body').removeClass('training-started'); + } + + updateTrainingState() { + // Set the initial state of the button and styles + if ($('body').hasClass('training-started')) { + this.startTraining(); + } else { + this.stopTraining(); + } + } +} + +var maneuverTrainingNavButtonHandler = new ManeuverTrainingHandler(); +export { maneuverTrainingNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/mobileController_c_logic.js b/teleop/htm/static/JS/mobileController/mobileController_c_logic.js index 278f0742..2fc5fc36 100644 --- a/teleop/htm/static/JS/mobileController/mobileController_c_logic.js +++ b/teleop/htm/static/JS/mobileController/mobileController_c_logic.js @@ -14,7 +14,6 @@ function setMobileCommand(x, y) { } function addDataToMobileCommand(newData) { - console.log(newData); // Iterate through the keys of the new data Object.keys(newData).forEach((key) => { // Only add the key if it doesn't already exist in mobileCommandJSON diff --git a/teleop/htm/static/JS/mobileController/mobileController_f_Inference.js b/teleop/htm/static/JS/mobileController/mobileController_f_Inference.js deleted file mode 100644 index 3bff4a96..00000000 --- a/teleop/htm/static/JS/mobileController/mobileController_f_Inference.js +++ /dev/null @@ -1,226 +0,0 @@ -import { addKeyToSentCommand } from "./mobileController_c_logic.js" -import { redraw, removeTriangles, changeTrianglesColor } from './mobileController_d_pixi.js'; -import { topTriangle, bottomTriangle } from "./mobileController_b_shape_triangle.js" - - -/** - * Handles toggle button interactions and manages WebSocket connections for real-time data updates. - */ -class InferenceToggleButton { - constructor(buttonId) { - this.toggleButton = document.getElementById(buttonId); - this.optionsContainer = document.getElementById('inference_options_container'); - this.toggleButtonContainer = document.getElementById('toggle_button_container'); - this.inferenceTrainingButton = document.getElementById('inference_training_toggle'); - this.InferenceAutoNavigationToggle = document.getElementById('inference_auto_navigation_toggle'); - this.InferenceAutoSpeedText = document.getElementById('inference_auto_speed'); - this.hideOptionsButton = document.getElementById('hide_options'); - this.speedElement = document.getElementById("inference_auto_speed"); - - this.logWS = {}; // Placeholder for WebSocket. - this.logWSmessage; - this.autoReconnectInterval = 9000; - this.initializeLogWS(); - /* - Means no smoothing for the other classes - false == working on normal mode - true == Inference is working on mobile controller - train == Inference is on training mode - auto == Inference is on training mode - */ - this._inferenceState = "false"; - this.currentAutoSpeed = 0 - this.buttonsEventListener() - } - - get getInferenceState() { - return this._inferenceState; - } - - buttonsEventListener() { - this.toggleButton.addEventListener('click', () => this.showInferenceOptions()); - this.hideOptionsButton.addEventListener('click', () => this.hideInferenceOptions()); - this.inferenceTrainingButton.addEventListener('click', () => this.handleInferenceTrainClick()); - this.InferenceAutoNavigationToggle.addEventListener('click', () => this.handleAutoNavigationClick()); - } - - hideInferenceOptions() { - this.InferenceAutoSpeedText.style.display = "none" - this.optionsContainer.style.display = 'none'; - this.toggleButtonContainer.style.display = 'flex'; - // Switch to driver_mode.teleop.direct mode - addKeyToSentCommand("button_b", 1) - // Turn off all the buttons to start state - if (this._inferenceState == "auto") { - this.handleAutoNavigationClick() - this._inferenceState = "false" - } else if (this._inferenceState == "train") { - this.handleInferenceTrainClick() - this._inferenceState = "false" - } - //Make it start with dark colour - redraw(undefined, true, true, true); - changeTrianglesColor(0x000000) - this._inferenceState = "false" - } - - showInferenceOptions() { - removeTriangles() - this.optionsContainer.style.display = 'flex'; - this.toggleButtonContainer.style.display = 'none'; - this._inferenceState = "true" - } - - handleAutoNavigationClick() { - this._inferenceState = this._inferenceState === "auto" ? "true" : "auto"; - // Now decide what to do based on the new state - if (this._inferenceState === "auto") { - this.startAutoNavigation(); - } else { - this.stopAutoNavigation(); - } - } - - startAutoNavigation() { - this.InferenceAutoNavigationToggle.innerText = "Stop Auto-navigation"; - redraw(undefined, true, true, false); - addKeyToSentCommand("button_y", 1); - topTriangle.changeText("Raise Speed", 25); - bottomTriangle.changeText("Lower Speed", 25); - this.InferenceAutoSpeedText.style.display = "block"; - this.hideOptionsButton.innerText = "Go to manual mode"; - this.inferenceTrainingButton.style.display = "none"; - } - - stopAutoNavigation() { - this.InferenceAutoNavigationToggle.innerText = "Start Auto-navigation"; - removeTriangles(); - addKeyToSentCommand("button_b", 1); - this.InferenceAutoSpeedText.style.display = "none"; - this.inferenceTrainingButton.style.display = "flex"; - this.hideOptionsButton.innerText = "Hide Options"; - this.speedElement.innerHTML = `0 Km/h`; - } - - handleInferenceTrainClick() { - this._inferenceState = this._inferenceState === "train" ? "true" : "train"; - if (this._inferenceState === "train") { - this.startTraining(); - } else { - this.stopTraining(); - } - } - - startTraining() { - this.inferenceTrainingButton.innerText = "Stop Training"; - redraw(undefined, true, false, true); - addKeyToSentCommand("button_y", 1); - this.hideOptionsButton.innerText = "Go to manual mode"; - this.InferenceAutoNavigationToggle.style.display = "none"; - } - - stopTraining() { - this.inferenceTrainingButton.innerText = "Start Training"; - removeTriangles(); - addKeyToSentCommand("button_b", 1); - this.hideOptionsButton.innerText = "Hide Options"; - this.InferenceAutoNavigationToggle.style.display = "flex"; - } - - /** - * Send message to increase to decrease the autopilot mode - * @param {string} touchedTriangle - name of the triangle selected - */ - handleSpeedControl(touchedTriangle) { - // Retrieve the current speed from the element - // Round the received number to the nearest 0.5 for consistency - let roundedSpeed = Math.round(this.logWSmessage.max_speed * 2) / 2; - if (!this.logWSmessage._is_on_autopilot) { - addKeyToSentCommand("button_y", 1); - console.log(this.logWSmessage._is_on_autopilot) - } - // Update the speed display, ensuring it always has one decimal place - this.speedElement.innerHTML = `${roundedSpeed.toFixed(1)} Km/h`; - if (touchedTriangle == "top" && roundedSpeed < 6) { - addKeyToSentCommand("arrow_up", 1); - } else if (touchedTriangle == "bottom" && roundedSpeed > 0) { - addKeyToSentCommand("arrow_down", 1); - } - } - - - initializeLogWS() { - let WSprotocol = document.location.protocol === 'https:' ? 'wss://' : 'ws://'; - let WSurl = `${WSprotocol}${document.location.hostname}:${document.location.port}/ws/log`; - this.logWS.websocket = new WebSocket(WSurl); - this.errorCount = 0; // Initialize error count - - this.logWS.websocket.onopen = (event) => { - console.log('Log WS connection opened'); - this.logWS.isWebSocketOpen = true; - this.errorCount = 0; // Reset error count on successful connection - this.sendInterval = setInterval(() => { - if (this.logWS.websocket.readyState === WebSocket.OPEN) { - this.logWS.websocket.send('{}'); - } else { - clearInterval(this.sendInterval); // Clear interval if not open - } - }, 40); - }; - - this.logWS.websocket.onmessage = (event) => { - let jsonLogWSmessage = JSON.parse(event.data); - this.decorate_server_message(jsonLogWSmessage) - // console.log('Log WS:', this.logWSmessage); - - if (this.logWSmessage._is_on_autopilot - && this.logWSmessage._has_passage == false - && this._inferenceState == "auto") { - changeTrianglesColor(0xFF0000) - } - - }; - - this.logWS.websocket.onerror = (error) => { - if (this.errorCount < 5) { - console.error('WebSocket Error:', error); - this.errorCount++; - } - }; - - this.logWS.websocket.onclose = (event) => { - console.log('Log WS connection closed'); - this.logWS.isWebSocketOpen = false; - clearInterval(this.sendInterval); // Ensure interval is cleared on close - // Automatically try to reconnect after a specified interval - setTimeout(() => this.checkAndReconnectWebSocket(), this.autoReconnectInterval); - }; - - - } - - /** - * Add fields related to INF state to the message - * @param {json} message Message received from log endpoint - */ - decorate_server_message(message) { - message._is_on_autopilot = message.ctl == 5; - message._has_passage = message.inf_total_penalty < 1; - if (message.geo_head == undefined) { - message.geo_head_text = 'n/a'; - } else { - message.geo_head_text = message.geo_head.toFixed(2); - } - this.logWSmessage = message - } - - checkAndReconnectWebSocket() { - if (!this.logWS.websocket || this.logWS.websocket.readyState === WebSocket.CLOSED) { - this.initializeLogWS(); - } - } - -} - - -export { InferenceToggleButton }; \ No newline at end of file diff --git a/teleop/htm/static/JS/mobileController/mobileController_f_auto_navigation.js b/teleop/htm/static/JS/mobileController/mobileController_f_auto_navigation.js deleted file mode 100644 index fb386561..00000000 --- a/teleop/htm/static/JS/mobileController/mobileController_f_auto_navigation.js +++ /dev/null @@ -1,70 +0,0 @@ -import { addDataToMobileCommand } from './mobileController_c_logic.js'; -import CTRL_STAT from './mobileController_z_state.js'; - -class AutoNavigationHandler { - constructor() {} - - initializeDOM() { - //TODO: it should hide the canvas but keep the squares visible - $('#mobile_controller_container .steeringWheel').hide(); - $('#mobile_controller_container .current_mode_button').show(); - $('#mobile_controller_container .current_mode_text').hide(); - $('#mobile_controller_container .trail_canvas').hide(); - $('.rover_speed').css('display', 'flex'); - $('.control_symbol').css('display', 'flex'); - $('.autopilot_status').show(); - $('.autopilot_status').css('color', 'black'); - $('#mobile_controller_container #forward_square .square_text').text('increase max speed'); - $('#mobile_controller_container #backward_square .square_text').text('decrease max speed'); - $('#mobile_controller_container .current_mode_button').text('start'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#451c58'); - $('#mobile_controller_container .current_mode_button').css('color', 'white'); - $('#mobile_controller_container .current_mode_button').css('box-shadow', 'none'); - this.bindButtonAction(); - } - bindButtonAction() { - $('#mobile_controller_container .current_mode_button').click((event) => { - const buttonText = $(event.target).text().toLowerCase(); - if (CTRL_STAT.currentPage === 'autopilot_link' && buttonText === 'start') { - console.log('Start'); - this.startAutoNavigation(); - } - if (CTRL_STAT.currentPage === 'autopilot_link' && buttonText === 'stop') { - this.stopAutoNavigation(); - } - }); - - document.querySelectorAll('.control_symbol').forEach((item) => { - item.addEventListener('touchstart', (event) => { - item.classList.add('active'); - const command = item.textContent.trim() === '+' ? 'increase' : 'decrease'; - this.handleSpeedControl(command); // 'this' now correctly refers to the instance of AutoNavigationHandler - }); - item.addEventListener('touchend', () => { - // Changed to arrow function - item.classList.remove('active'); - }); - }); - } - startAutoNavigation() { - addDataToMobileCommand({ button_y: 1 }); - } - - stopAutoNavigation() { - addDataToMobileCommand({ button_b: 1 }); - } - - /** - * Send message to increase to decrease the autopilot mode - */ - handleSpeedControl(cmd) { - // Update the speed display, ensuring it always has one decimal place - if (cmd == 'increase') { - addDataToMobileCommand({ arrow_up: 1 }); - } else if (cmd == 'decrease') { - addDataToMobileCommand({ arrow_down: 1 }); - } - } -} -var autoNavigationNavButtonHandler = new AutoNavigationHandler(); -export { autoNavigationNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/mobileController_f_confidence.js b/teleop/htm/static/JS/mobileController/mobileController_f_confidence.js deleted file mode 100644 index 6b7493d4..00000000 --- a/teleop/htm/static/JS/mobileController/mobileController_f_confidence.js +++ /dev/null @@ -1,159 +0,0 @@ -import CTRL_STAT from './mobileController_z_state.js'; -class ConfidenceHandler { - constructor() { - this.confidenceWS = {}; - } - - initializeDOM() { - $('.current_mode_text').show(); - $('.current_mode_text').css('text-align', 'center'); - $('#mobile_controller_container .steeringWheel').hide(); - $(' .current_mode_button').show(); - $('#mobile_controller_container .square').children().show(); - $('.control_symbol').css('display', 'none'); - $('.stop_text').css('display', 'none'); - $('#mobile_controller_container #forward_square .square_text').text('forward'); - $('#mobile_controller_container #backward_square .square_text').text('backward'); - - $('.current_mode_button').text('recognize'); - $('.current_mode_button').css('background-color', '#451c58'); - $('.current_mode_button').css('color', 'white'); - $('.current_mode_button').css('box-shadow', 'none'); - this.initializeConfidenceWS(); - this.bindButtonAction(); - } - - bindButtonAction() { - $('.current_mode_button').click((event) => { - const buttonText = $(event.target).text().toLowerCase(); - if (CTRL_STAT.currentPage == 'map_recognition_link' && buttonText == 'recognize') { - this.sendSwitchConfidenceRequest('start_confidence'); - this.toggleButtonAppearance('stop'); - } else if (CTRL_STAT.currentPage == 'map_recognition_link' && buttonText == 'stop') { - this.sendSwitchConfidenceRequest('stop_confidence'); - this.toggleButtonAppearance('start'); - } else if (CTRL_STAT.currentPage == 'map_recognition_link' && buttonText == 'return') { - $('#mobile_controller_container #backward_square').show(); - $('#mobile_controller_container #forward_square').show(); - $('#map_frame, #top_layer_iframe').hide(); - this.toggleButtonAppearance('start'); - } - }); - } - - /** - * Sends a command to the server via WebSocket. - * @param {string} command The command to be sent to the server. - */ - sendSwitchConfidenceRequest(command) { - console.log(command); - if (this.confidenceWS.websocket && this.confidenceWS.websocket.readyState === WebSocket.OPEN) { - console.log(command); - this.confidenceWS.websocket.send(command); - } else { - console.error('Confidence websocket is not open. Command not sent. Attempting to reconnect...'); - this.checkAndReconnectWebSocket(); - } - } - - toggleButtonAppearance(cmd) { - if (cmd == 'start') { - $('.current_mode_button').text('recognize'); - $('.current_mode_button').css('background-color', '#451c58'); - $('.current_mode_button').css('color', 'white'); - $('.current_mode_button').css('box-shadow', 'none'); - } else if (cmd == 'stop') { - $('.current_mode_button').text('stop'); - $('.current_mode_button').css('background-color', '#f41e52'); - $('.current_mode_button').css('border', 'none'); - $('#mobile_controller_container .square').children().show(); - $('.stop_text, .control_symbol').hide(); - } else if (cmd == 'return') { - $('.current_mode_button').text('return'); - $('.current_mode_button').css('background-color', '#ffffff'); - $('.current_mode_button').css('color', '#451c58'); - $('.current_mode_button').css('border', ''); - } else if (cmd == 'show_result') { - $('.current_mode_button').show(); - $('.current_mode_button').text('Show result'); - $('.current_mode_button').css('border', 'none'); - } - } - - /** - * Initializes the WebSocket connection for real-time data updates and sets up event listeners. - */ - initializeConfidenceWS() { - let WSprotocol = document.location.protocol === 'https:' ? 'wss://' : 'ws://'; - this.currentURL = `${document.location.protocol}`; - let WSurl = `${WSprotocol}${document.location.hostname}:${document.location.port}/ws/switch_confidence`; - this.confidenceWS.websocket = new WebSocket(WSurl); - - this.confidenceWS.websocket.onopen = (event) => { - this.confidenceWS.isWebSocketOpen = true; - }; - - this.confidenceWS.websocket.onmessage = (event) => { - this.updateButtonState(event.data); - }; - - this.confidenceWS.websocket.onclose = (event) => { - console.log('Confidence websocket connection closed'); - this.confidenceWS.isWebSocketOpen = false; - // Automatically try to reconnect after a specified interval - setTimeout(() => this.checkAndReconnectWebSocket(), 500); - }; - } - - /** - * Checks the WebSocket's current state and attempts to reconnect if it's closed. - */ - checkAndReconnectWebSocket() { - if (!this.confidenceWS.websocket || this.confidenceWS.websocket.readyState === WebSocket.CLOSED) { - this.initializeConfidenceWS(); - } - } - - - loadMapIntoIframe(url) { - const iframeSelector = '#map_frame, #top_layer_iframe'; - - // Set the source of the iframe - $(iframeSelector).attr('src', `${this.currentURL}/overview_confidence/${url}`); - - // Fade in the iframe when the content is ready to display - $(iframeSelector).fadeIn(500); // 500ms for the fade-in effect -} - - /** - * Updates the button's appearance based on the received WebSocket message. - * @param {string} message The message received from the WebSocket. - */ - updateButtonState(message) { - // console.log(message); - if (message === 'loading') { - $('.current_mode_state').text('Loading...'); - $('.current_mode_state').css('color', '#FF8A00'); - $('.current_mode_button').hide(); - } else if (message.endsWith('.html')) { - // Extract the filename from the message - const filename = message.match(/[\w-]+\.html$/)[0]; - $('.current_mode_state').hide(); - $('#mobile_controller_container #backward_square').hide(); - $('#mobile_controller_container #forward_square').hide(); - this.toggleButtonAppearance('show_result'); - - $('.current_mode_button') - .off('click') - .click(() => { - // This will load the HTML content into the `forward_square` div without redirecting - - this.loadMapIntoIframe(filename); - this.bindButtonAction(); - this.toggleButtonAppearance('return'); - }); - } - } -} -var confidenceNavButtonHandler = new ConfidenceHandler(); -export { confidenceNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/mobileController_f_following.js b/teleop/htm/static/JS/mobileController/mobileController_f_following.js deleted file mode 100644 index bdebd022..00000000 --- a/teleop/htm/static/JS/mobileController/mobileController_f_following.js +++ /dev/null @@ -1,190 +0,0 @@ -import CTRL_STAT from './mobileController_z_state.js'; - -class FollowingHandler { - constructor(buttonId) { - this.toggleButton = $(buttonId); - this.errorLogged = false; // Add this line to initialize the error flag - this.initialSetup(); - this.startPolling(); - } - - initializeDOM() { - // $("#mobile_controller_container .current_mode_state").text('Loading...') - $('#mobile_controller_container .current_mode_button').show(); - $('#mobile_controller_container .current_mode_button').text('start following'); - $('#mobile_controller_container .middle_section').hide(); - $('#mobile_controller_container .square').hide(); - - this.sendSwitchFollowingRequest('show_image'); - - this.bindButtonAction(); - // It should send the stopping command `sendSwitchFollowingRequest("stop_following")` when I switch the pages - this.initializeCanvas(); - } - bindButtonAction() { - $('#mobile_controller_container .current_mode_button').click(() => { - if (CTRL_STAT.followingState === 'inactive') { - this.sendSwitchFollowingRequest('show_image'); - } else if (CTRL_STAT.followingState === 'image') { - this.sendSwitchFollowingRequest('start_following'); - } else if (CTRL_STAT.followingState === 'active') { - this.sendSwitchFollowingRequest('show_image'); - } - }); - } - - initializeCanvas() { - this.canvas = document.getElementById('following_imageCanvas'); - if (this.canvas) { - this.ctx = this.canvas.getContext('2d'); - } else { - setTimeout(() => this.initializeCanvas(), 500); // Retry after 500ms using arrow function - } - } - - initialSetup() { - window.addEventListener('resize', () => this.resizeCanvas()); - } - - sendSwitchFollowingRequest(command) { - console.log('called with this', command); - fetch('/fol_handler', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: `command=${encodeURIComponent(command)}`, - }) - .then((response) => response.json()) - .catch((error) => console.error('Error sending command:', error)); - } - - startPolling() { - setInterval(() => { - if (CTRL_STAT.currentPage == 'follow_link') { - fetch('/fol_handler', { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) - .then((response) => response.json()) - .then((data) => { - const previousState = CTRL_STAT.followingState; - this.assignFollowingState(data.following_status); - this.toggleButtonAppearance(); - if (previousState !== CTRL_STAT.followingState) { - } - this.errorLogged = false; - }) - .catch((error) => { - if (!this.errorLogged) { - console.error('Error polling backend:', error); - this.errorLogged = true; - } - }); - } - }, 500); - } - - assignFollowingState(backendCommand) { - // console.log(backendCommand, CTRL_STAT.followingState); - switch (backendCommand) { - case 'active': - CTRL_STAT.followingState = 'active'; // The system is actively following - break; - case 'image': - CTRL_STAT.followingState = 'image'; // The system is doing inference on the current stream from the camera, but not sending movement commands - break; - case 'inactive': - CTRL_STAT.followingState = 'inactive'; // The system is ready and not following - break; - case 'loading': - CTRL_STAT.followingState = 'loading'; // The system is loading - break; - default: - console.log('Following: Unknown command received from the backend:', backendCommand); - } - } - - toggleButtonAppearance() { - if (CTRL_STAT.currentPage == 'follow_link') { - if (CTRL_STAT.followingState == 'image') { - this.resizeCanvas(); - this.showCanvas(); - $('#mobile_controller_container .current_mode_state').hide(); - $('#mobile_controller_container .square').hide(); - $('#mobile_controller_container .current_mode_button').text('start following'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#ffffff'); - $('#mobile_controller_container .current_mode_button').css('border', ''); - } else if (CTRL_STAT.followingState == 'active') { - this.resizeCanvas(); - this.showCanvas(); - $('#mobile_controller_container .current_mode_state').hide(); - $('#mobile_controller_container .square').hide(); - $('#mobile_controller_container .current_mode_button').text('stop'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#f41e52'); - $('#mobile_controller_container .current_mode_button').css('border', 'none'); - } else if (CTRL_STAT.followingState == 'inactive') { - $('#mobile_controller_container .square').show(); - $('#mobile_controller_container .current_mode_state').hide(); - $('#mobile_controller_container .current_mode_button').text('start following'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#ffffff'); - $('#mobile_controller_container .current_mode_button').css('border', ''); - - this.hideCanvas(); - // this.toggleButton.innerText = 'Start Following'; - // this.toggleButton.style.backgroundColor = '#67b96a'; - } else if (CTRL_STAT.followingState == 'loading') { - this.hideCanvas(); - $('#mobile_controller_container .current_mode_state').text('Loading...'); - // this.toggleButton.style.backgroundColor = '#ffa500'; - } - } - } - - showCanvas() { - if (this.canvas) { - this.canvas.style.display = 'block'; - if (!this.streamActive && !this.intervalId) { - this.streamActive = true; - this.intervalId = setInterval(() => this.refreshImage(), 30); // Start streaming - } - } - } - - hideCanvas() { - if (this.canvas) { - this.canvas.style.display = 'none'; - if (this.streamActive && this.intervalId) { - clearInterval(this.intervalId); // Stop streaming - this.intervalId = null; - this.streamActive = false; - } - } - } - - refreshImage() { - if (!this.streamActive) return; // Do not proceed if streaming is not active - - const img = new Image(); - img.onload = () => { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // Clear previous image - this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height); // Draw new image - }; - img.src = '/latest_image?' + new Date().getTime(); // Include cache busting to prevent loading from cache - } - - resizeCanvas() { - if (this.canvas) { - let maxWidth = window.innerWidth * 0.8; // 80% of the viewport width - if (maxWidth > 640) maxWidth = 640; // Ensuring the width does not exceed 640 pixels - const maxHeight = (maxWidth * 3) / 4; // Maintain 4:3 ratio - - this.canvas.width = maxWidth; - this.canvas.height = maxHeight; - this.canvas.style.width = `${maxWidth}px`; - this.canvas.style.height = `${maxHeight}px`; - } - } -} - -var followingNavButtonHandler = new FollowingHandler('.hamburger_menu_nav a#follow_link'); - -export { followingNavButtonHandler }; diff --git a/teleop/htm/static/JS/mobileController/mobileController_f_maneuver_training.js b/teleop/htm/static/JS/mobileController/mobileController_f_maneuver_training.js deleted file mode 100644 index 39606553..00000000 --- a/teleop/htm/static/JS/mobileController/mobileController_f_maneuver_training.js +++ /dev/null @@ -1,54 +0,0 @@ -import { addDataToMobileCommand } from './mobileController_c_logic.js'; -import CTRL_STAT from './mobileController_z_state.js'; - -class ManeuverTrainingHandler { - constructor() {} - - initializeDOM() { - //TODO: it should hide the canvas but keep the squares visible - $('#mobile_controller_container .steeringWheel').hide(); - $('#mobile_controller_container #backward_square').children().hide(); - $('.control_symbol').css('display', 'none'); - $('#mobile_controller_container #backward_square .trail_canvas').hide(); - - $('#mobile_controller_container #forward_square .square_text').text('forward'); - - $('#mobile_controller_container .current_mode_button').show(); - $('#mobile_controller_container .current_mode_button').text('start'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#451c58'); - $('#mobile_controller_container .current_mode_button').css('color', 'white'); - $('#mobile_controller_container .current_mode_button').css('box-shadow', 'none'); - $('#mobile_controller_container #backward_square').addClass('maneuver_square'); - - this.bindButtonAction(); - } - bindButtonAction() { - $('#mobile_controller_container .current_mode_button').click((event) => { - const buttonText = $(event.target).text().toLowerCase(); - if (CTRL_STAT.currentPage === 'ai_training_link' && buttonText === 'start') { - this.startTraining(); - } - if (CTRL_STAT.currentPage === 'ai_training_link' && buttonText === 'stop') { - this.stopTraining(); - } - }); - } - - startTraining() { - addDataToMobileCommand({ button_y: 1 }); - $('#mobile_controller_container .current_mode_button').text('stop'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#f41e52'); - $('#mobile_controller_container .current_mode_button').css('border', 'none'); - } - - stopTraining() { - addDataToMobileCommand({ button_b: 1 }); - $('#mobile_controller_container .current_mode_button').text('start'); - $('#mobile_controller_container .current_mode_button').css('background-color', '#451c58'); - $('#mobile_controller_container .current_mode_button').css('color', 'white'); - $('#mobile_controller_container .current_mode_button').css('box-shadow', 'none'); - } -} - -var maneuverTrainingNavButtonHandler = new ManeuverTrainingHandler(); -export { maneuverTrainingNavButtonHandler }; diff --git a/teleop/htm/static/JS/router.js b/teleop/htm/static/JS/router.js index e15e8c6e..0973abd0 100644 --- a/teleop/htm/static/JS/router.js +++ b/teleop/htm/static/JS/router.js @@ -1,10 +1,10 @@ import { isMobileDevice } from './Index/index_a_utils.js'; import { roverUI } from './Index/index_c_screen.js'; import { setupMobileController } from './mobileController/mobileController_a_app.js'; -import { autoNavigationNavButtonHandler } from './mobileController/mobileController_f_auto_navigation.js'; -import { confidenceNavButtonHandler } from './mobileController/mobileController_f_confidence.js'; -import { followingNavButtonHandler } from './mobileController/mobileController_f_following.js'; -import { maneuverTrainingNavButtonHandler } from './mobileController/mobileController_f_maneuver_training.js'; +import { autoNavigationNavButtonHandler } from './mobileController/feature/mobileController_f_auto_navigation.js'; +import { confidenceNavButtonHandler } from './mobileController/feature/mobileController_f_confidence.js'; +import { followingNavButtonHandler } from './mobileController/feature/mobileController_f_following.js'; +import { maneuverTrainingNavButtonHandler } from './mobileController/feature/mobileController_f_maneuver_training.js'; import CTRL_STAT from './mobileController/mobileController_z_state.js'; import { ControlSettings } from './userMenu/menu_controls.js'; import { LogBox } from './userMenu/menu_logbox.js'; @@ -78,7 +78,6 @@ export class Router { const activeNavLinkImageSrc = $(`#${selectedLinkId} img`).attr('src'); if (activeNavLinkImageSrc) { $('.current_mode_img').attr('src', activeNavLinkImageSrc); - $('.current_mode_text').text(this.mode_to_nav_link[selectedLinkId]); } else { console.error('No image found for ID:', selectedLinkId); } @@ -153,13 +152,29 @@ export class Router { } /** - * Actions that are bonded to the navbar buttons only. They will control the switch for the features + * Removes all mode-related classes from the body element dynamically. + */ + resetStyles() { + // Define the pattern for mode-related classes + const modeClassPattern = /-\b(feature|mode)\b/; + + // Get the current list of classes on the body element + const bodyClasses = $('body').attr('class') ? $('body').attr('class').split(/\s+/) : []; + + // Filter and remove all classes that match the pattern + bodyClasses.forEach((className) => { + if (modeClassPattern.test(className)) { + $('body').removeClass(className); + } + }); + } + + /** + * Actions that are bonded to the navbar buttons only. */ assignNavButtonActions(navLink) { - //TODO: should add a filter here to remove all the styles related to the lat mode and reset them to the default values. So the new mode would only add its new styles on DOM. - $('#mobile_controller_container #backward_square').removeClass('maneuver_square'); - $('#mobile_controller_container .trail_canvas').show(); - $('#map_frame').hide(); + this.resetStyles(); + if (navLink == 'follow_link' && isMobileDevice()) followingNavButtonHandler.initializeDOM(); else if (navLink == 'autopilot_link') autoNavigationNavButtonHandler.initializeDOM(); else if (navLink == 'ai_training_link') maneuverTrainingNavButtonHandler.initializeDOM(); diff --git a/teleop/htm/templates/index.html b/teleop/htm/templates/index.html index ae40f8d0..2d8e1541 100644 --- a/teleop/htm/templates/index.html +++ b/teleop/htm/templates/index.html @@ -5,14 +5,15 @@ teleop - - + + + diff --git a/teleop/htm/templates/mobile_controller_ui.html b/teleop/htm/templates/mobile_controller_ui.html index 7dbaaefe..4da3ceec 100644 --- a/teleop/htm/templates/mobile_controller_ui.html +++ b/teleop/htm/templates/mobile_controller_ui.html @@ -1,7 +1,7 @@
-

manual drive

+

0

@@ -10,10 +10,10 @@
- +
- +

@@ -24,14 +24,14 @@
-
forward
+

drag your finger to this area to force the robot to stop
-
backward
+

drag your finger to this area to force the robot to stop
diff --git a/teleop/htm/templates/normal_ui.html b/teleop/htm/templates/normal_ui.html index 98ef84ff..25b8bfb3 100644 --- a/teleop/htm/templates/normal_ui.html +++ b/teleop/htm/templates/normal_ui.html @@ -48,9 +48,9 @@
-

manual drive

+

- +

0