From 238043cd235bc286b791c749f18fcb0217d2c04c Mon Sep 17 00:00:00 2001 From: Fred Clausen <43556888+fredclausen@users.noreply.github.com> Date: Sun, 2 Jun 2024 01:41:20 -0600 Subject: [PATCH 01/40] wip --- sh-frontend/src/app/webapp.rs | 52 ++++++++++++++----- sh-frontend/src/common/mod.rs | 1 + sh-frontend/src/common/wssprops.rs | 12 +++++ .../src/components/layout_components/live.rs | 8 +-- sh-frontend/src/components/pages/settings.rs | 8 +-- .../setting_components/sh_app_config.rs | 8 ++- sh-frontend/src/services/websocket.rs | 2 + 7 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 sh-frontend/src/common/wssprops.rs diff --git a/sh-frontend/src/app/webapp.rs b/sh-frontend/src/app/webapp.rs index d5d18ab..8878665 100644 --- a/sh-frontend/src/app/webapp.rs +++ b/sh-frontend/src/app/webapp.rs @@ -3,24 +3,48 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::thread::Scope; + use crate::components::layout_components::footer::Footer; use crate::components::layout_components::live::Live; use crate::components::layout_components::nav::Nav; -use crate::services::websocket::ShWebSocketComponent; +use crate::services::websocket::{ShWebSocketComponent, Msg, WsAction}; +use sh_common::UserWssMessage; use yew::prelude::*; -#[function_component(App)] -pub fn app() -> Html { - html! { - <> - -
-
- +pub struct App {} + +impl Component for App { + type Message = crate::services::websocket::Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self {} + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + log::info!("App update: {:?}", msg); + false } + + fn view(&self, ctx: &Context) -> Html { + let send_data_to_wss = ctx.link().callback(move |input: UserWssMessage| { + log::debug!("Got a message. Sending up the chain to the websocket!"); + Msg::WsAction(WsAction::SendData(input)) + }); + + html! { + <> + +
+
+ + } + } + } diff --git a/sh-frontend/src/common/mod.rs b/sh-frontend/src/common/mod.rs index f8c1bcf..e71e3f5 100644 --- a/sh-frontend/src/common/mod.rs +++ b/sh-frontend/src/common/mod.rs @@ -4,3 +4,4 @@ // https://opensource.org/licenses/MIT. pub mod panels; +pub mod wssprops; \ No newline at end of file diff --git a/sh-frontend/src/common/wssprops.rs b/sh-frontend/src/common/wssprops.rs new file mode 100644 index 0000000..2eb16bf --- /dev/null +++ b/sh-frontend/src/common/wssprops.rs @@ -0,0 +1,12 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use sh_common::UserWssMessage; +use yew::prelude::*; + +#[derive(Properties, Clone, PartialEq)] +pub struct WssCommunicationProps { + pub send_message: Callback, +} \ No newline at end of file diff --git a/sh-frontend/src/components/layout_components/live.rs b/sh-frontend/src/components/layout_components/live.rs index 108a16e..e992f4c 100644 --- a/sh-frontend/src/components/layout_components/live.rs +++ b/sh-frontend/src/components/layout_components/live.rs @@ -9,7 +9,7 @@ use crate::components::pages::help::ShHelp; use crate::components::pages::settings::ShSettings; use crate::components::pages::stats::ShStatistics; use crate::services::saved_state::WebAppState; -use crate::{common::panels::Panels, services::temp_state::WebAppStateTemp}; +use crate::{common::panels::Panels, services::temp_state::WebAppStateTemp, common::wssprops::WssCommunicationProps}; use yew::prelude::*; use yew_hooks::{use_event_with_window, use_visible}; use yewdux::prelude::*; @@ -18,7 +18,7 @@ use yewdux::prelude::*; // checking the old value vs the new one and setting the panel state if it's changed. This flags a re-render /// Home page #[function_component(Live)] -pub fn live() -> Html { +pub fn live(props: &WssCommunicationProps) -> Html { let (_state_local, dispatch_local) = use_store::(); let (_state, dispatch) = use_store::(); log::debug!("Re-rendering live page"); @@ -45,7 +45,7 @@ pub fn live() -> Html { match *right_panel { Panels::Messages => html! { }, Panels::Map => html! { }, - Panels::Settings => html! { }, + Panels::Settings => html! { }, Panels::Help => html! { }, Panels::Stats => html! { }, Panels::None => panic!("Right Panel is none!!!"), @@ -56,7 +56,7 @@ pub fn live() -> Html { match *left_panel { Panels::Messages => html! { }, Panels::Map => html! { }, - Panels::Settings => html! { }, + Panels::Settings => html! { }, Panels::Help => html! { }, Panels::Stats => html! { }, Panels::None => panic!("Left Panel is none!!!"), diff --git a/sh-frontend/src/components/pages/settings.rs b/sh-frontend/src/components/pages/settings.rs index c340833..5d89b8f 100644 --- a/sh-frontend/src/components/pages/settings.rs +++ b/sh-frontend/src/components/pages/settings.rs @@ -3,19 +3,19 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::components::setting_components::{ +use crate::{common::wssprops::WssCommunicationProps, components::setting_components::{ sh_app_config::ShAppConfig, sh_data_sources::ShDataSourcesConfig, sh_enabled_data_sources::ShEnabledDataSourcesConfig, sh_map::ShMapConfig, -}; +}}; use yew::prelude::*; /// Home page #[function_component(ShSettings)] -pub fn settings() -> Html { +pub fn settings(props: &WssCommunicationProps) -> Html { html! { <>
- + diff --git a/sh-frontend/src/components/setting_components/sh_app_config.rs b/sh-frontend/src/components/setting_components/sh_app_config.rs index 1ad760f..cc88650 100644 --- a/sh-frontend/src/components/setting_components/sh_app_config.rs +++ b/sh-frontend/src/components/setting_components/sh_app_config.rs @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use crate::common::wssprops::WssCommunicationProps; use crate::components::alerts::alert_error::AlertError; use crate::components::input::input_field::{InputField, InputFieldType}; use crate::services::temp_state::WebAppStateTemp; @@ -38,7 +39,7 @@ impl From for ButtonAction { } #[function_component(ShAppConfig)] -pub fn sh_app_config() -> Html { +pub fn sh_app_config(props: &WssCommunicationProps) -> Html { let config = use_selector(|state: &WebAppStateTemp| state.config.clone()); let (state, dispatch) = use_store::(); let (temp_state, temp_dispatch) = use_store::(); @@ -58,6 +59,8 @@ pub fn sh_app_config() -> Html { "warn".to_string(), ]; + let local_props = props.clone(); + let onsubmit = { let config = config.clone(); let database_url_node = database_url_node.clone(); @@ -104,6 +107,9 @@ pub fn sh_app_config() -> Html { UserMessageTypes::UserUpdateAppConfig, MessageData::ShAppConfig(new_config), ); + + // send a message using the props callback + local_props.send_message.emit(message); } } diff --git a/sh-frontend/src/services/websocket.rs b/sh-frontend/src/services/websocket.rs index 1ea2fef..bf0c849 100644 --- a/sh-frontend/src/services/websocket.rs +++ b/sh-frontend/src/services/websocket.rs @@ -9,6 +9,7 @@ use yewdux::Dispatch; // https://github.com/security-union/yew-websocket/ +#[derive(Debug)] pub enum WsAction { Connect, SendData(UserWssMessage), @@ -16,6 +17,7 @@ pub enum WsAction { Lost, } +#[derive(Debug)] pub enum Msg { WsAction(WsAction), WsReady(Result), From 0bc79af5a2f278051d93463a04da0da565b7d971 Mon Sep 17 00:00:00 2001 From: Fred Clausen <43556888+fredclausen@users.noreply.github.com> Date: Sun, 2 Jun 2024 02:07:18 -0600 Subject: [PATCH 02/40] wip --- sh-frontend/src/app/webapp.rs | 220 ++++++++++++++++-- sh-frontend/src/common/mod.rs | 2 +- sh-frontend/src/common/wssprops.rs | 2 +- .../src/components/layout_components/live.rs | 5 +- sh-frontend/src/components/pages/settings.rs | 11 +- .../setting_components/sh_app_config.rs | 2 +- sh-frontend/src/services/mod.rs | 1 - sh-frontend/src/services/websocket.rs | 184 --------------- 8 files changed, 221 insertions(+), 206 deletions(-) delete mode 100644 sh-frontend/src/services/websocket.rs diff --git a/sh-frontend/src/app/webapp.rs b/sh-frontend/src/app/webapp.rs index 8878665..f7dc2fc 100644 --- a/sh-frontend/src/app/webapp.rs +++ b/sh-frontend/src/app/webapp.rs @@ -3,31 +3,190 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::thread::Scope; - use crate::components::layout_components::footer::Footer; use crate::components::layout_components::live::Live; use crate::components::layout_components::nav::Nav; -use crate::services::websocket::{ShWebSocketComponent, Msg, WsAction}; -use sh_common::UserWssMessage; -use yew::prelude::*; +use crate::services::temp_state::WebAppStateTemp; +use anyhow::Error; +use sh_common::{ + MessageData, ServerMessageTypes, ServerWssMessage, UserMessageTypes, UserWssMessage, +}; +use yew::{html, Component, Context, Html}; +use yew_websocket::websocket::{WebSocketService, WebSocketStatus, WebSocketTask}; +use yewdux::Dispatch; + +// https://github.com/security-union/yew-websocket/ + +#[derive(Debug)] +pub enum WsAction { + Connect, + SendData(UserWssMessage), + Disconnect, + Lost, +} + +#[derive(Debug)] +pub enum Msg { + WsAction(WsAction), + WsReady(Result), +} + +impl From for Msg { + fn from(action: WsAction) -> Self { + Self::WsAction(action) + } +} -pub struct App {} +pub struct App { + pub fetching: bool, + pub ws: Option, + pub dispatch: Dispatch, +} impl Component for App { - type Message = crate::services::websocket::Msg; + type Message = Msg; type Properties = (); fn create(_ctx: &Context) -> Self { - Self {} + let dispatch = Dispatch::::global(); + + Self { + fetching: false, + ws: None, + dispatch, + } } - fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { - log::info!("App update: {:?}", msg); - false + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::WsAction(action) => match action { + WsAction::Connect => { + let callback = ctx.link().callback(|data| Msg::WsReady(data)); + let notification = ctx.link().batch_callback(|status| match status { + WebSocketStatus::Opened => { + let initial_message = UserWssMessage::new( + UserMessageTypes::UserRequestConfig, + MessageData::NoData, + ); + Some(WsAction::SendData(initial_message).into()) + } + WebSocketStatus::Closed | WebSocketStatus::Error => { + Some(WsAction::Lost.into()) + } + }); + let task = WebSocketService::connect_text( + "ws://127.0.0.1:3000/sdre-hub", + callback, + notification, + ) + .unwrap(); + self.ws = Some(task); + false + } + WsAction::SendData(data) => { + log::debug!("Sending data: {:?}", data); + log::debug!("Sending data: {:?}", serde_json::to_string(&data).unwrap()); + let serialized_data = serde_json::to_string(&data).unwrap(); + self.ws + .as_mut() + .unwrap() + .send(serde_json::to_string(&serialized_data).unwrap()); + + if self.fetching == false { + self.fetching = true; + self.dispatch.reduce_mut(|state| { + state.websocket_connected = true; + }); + } + false + } + WsAction::Disconnect => { + self.ws.take(); + log::info!( + "WebSocket connection disconnected. Why? This should be unreachable" + ); + self.fetching = false; + self.dispatch.reduce_mut(|state| { + state.config = None; + state.websocket_connected = false; + }); + + false + } + WsAction::Lost => { + self.ws = None; + log::error!("WebSocket connection lost. Reconnecting"); + self.fetching = false; + // reconnect + ctx.link().send_message(WsAction::Connect); + self.dispatch.reduce_mut(|state| { + state.config = None; + state.websocket_connected = false; + }); + + false + } + }, + Msg::WsReady(response) => { + log::debug!("Received data: {:?}", response); + + if response.is_err() { + log::error!("Error: {:?}", response.err().unwrap()); + return false; + } + + let data = response.unwrap(); + // remove the first and last characters + + log::debug!( + "Received text message after trimming and replacement: {}", + data + ); + + let data_deserialized: ServerWssMessage = match serde_json::from_str(&data) { + Ok(message) => message, + Err(e) => { + log::error!("Error deserializing message: {:?}", e); + return false; + } + }; + + match data_deserialized.get_message_type() { + ServerMessageTypes::ServerResponseConfig => { + log::debug!("Received config message"); + self.dispatch + .reduce_mut(|state| match data_deserialized.get_data() { + MessageData::ShConfig(config) => { + state.config = Some(config.clone()); + } + _ => { + log::error!("Received invalid data type"); + } + }); + } + _ => { + log::error!("Received unknown message: {:?}", data_deserialized); + } + } + + false + } + } } fn view(&self, ctx: &Context) -> Html { + if self.ws.is_none() { + ctx.link().send_message(WsAction::Connect); + log::info!("Connecting to WebSocket"); + } else { + log::info!("WebSocket is connected"); + ctx.link() + .send_message(WsAction::SendData(UserWssMessage::new( + UserMessageTypes::UserRequestConfig, + MessageData::NoData, + ))); + } + let send_data_to_wss = ctx.link().callback(move |input: UserWssMessage| { log::debug!("Got a message. Sending up the chain to the websocket!"); Msg::WsAction(WsAction::SendData(input)) @@ -35,7 +194,6 @@ impl Component for App { html! { <> -
} diff --git a/sh-frontend/src/components/alerts/alert_config.rs b/sh-frontend/src/components/alerts/alert_config.rs new file mode 100644 index 0000000..0d184c5 --- /dev/null +++ b/sh-frontend/src/components/alerts/alert_config.rs @@ -0,0 +1,66 @@ +// Copyright (C) 2024 Fred Clausen +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use yew::prelude::*; +use yew_alert::Alert; +use super::AlertPropsAlternate; + +// FIXME: Before ridding of tailwind the "position" part of this prop needs us to implement some more CSS classes. See: https://github.com/next-rs/yew-alert/blob/37da6d37d10cb32dc778d4f7a642800eb8188175/src/lib.rs#L233 + +#[function_component(AlertConfig)] +pub fn alert_config(props: &AlertPropsAlternate) -> Html { + let show_alert_as_state = use_state_eq(|| props.show_alert); + let on_cancel = props.on_cancel.clone(); + let show_cancel = props.show_cancel_button; + let show_confirm = props.show_confirm_button; + let title = props.title; + let message = props.message; + let timeout = props.timeout; + let cancel_button_class = props.cancel_button_class; + let confirm_button_class = props.confirm_button_class; + + let dismiss_alert = { + let show_alert_as_state = show_alert_as_state.clone(); + + Callback::from(move |_| { + show_alert_as_state.set(false) + }) + }; + + log::debug!("AlertConfig: show_alert: {:?}", props.show_alert); + log::debug!("AlertConfig: show_alert_as_state: {:?}", show_alert_as_state.clone()); + let new_show_alert = show_alert_as_state.clone(); + + use_effect_with(props.counter, move |_show_alert| { + show_alert_as_state.set(true) + }); + + html! { + + } +} diff --git a/sh-frontend/src/components/alerts/alert_error.rs b/sh-frontend/src/components/alerts/alert_error.rs index b9556d7..adb28c6 100644 --- a/sh-frontend/src/components/alerts/alert_error.rs +++ b/sh-frontend/src/components/alerts/alert_error.rs @@ -5,34 +5,12 @@ use yew::prelude::*; use yew_alert::Alert; - -#[derive(Clone, PartialEq, Properties)] -pub struct AlertErrorProps { - #[prop_or_default] - pub message: &'static str, - #[prop_or_default] - pub title: &'static str, - pub show_alert: UseStateHandle, - #[prop_or(Callback::noop())] - pub on_confirm: Callback<()>, - #[prop_or(Callback::noop())] - pub on_cancel: Callback<()>, - #[prop_or(5000)] - pub timeout: u32, - #[prop_or("button")] - pub cancel_button_class: &'static str, - #[prop_or("button")] - pub confirm_button_class: &'static str, - #[prop_or_default] - pub show_cancel_button: bool, - #[prop_or(true)] - pub show_confirm_button: bool, -} +use super::AlertProps; // FIXME: Before ridding of tailwind the "position" part of this prop needs us to implement some more CSS classes. See: https://github.com/next-rs/yew-alert/blob/37da6d37d10cb32dc778d4f7a642800eb8188175/src/lib.rs#L233 #[function_component(AlertError)] -pub fn alert_error(props: &AlertErrorProps) -> Html { +pub fn alert_error(props: &AlertProps) -> Html { let show_alert = props.show_alert.clone(); let on_confirm = props.on_confirm.clone(); let on_cancel = props.on_cancel.clone(); diff --git a/sh-frontend/src/components/alerts/mod.rs b/sh-frontend/src/components/alerts/mod.rs index 90e3dd9..3dec5a3 100644 --- a/sh-frontend/src/components/alerts/mod.rs +++ b/sh-frontend/src/components/alerts/mod.rs @@ -6,3 +6,55 @@ // https://github.com/next-rs/yew-alert pub mod alert_error; +pub mod alert_config; + +use yew::prelude::*; + +#[derive(Clone, PartialEq, Properties)] +pub struct AlertProps { + #[prop_or_default] + pub message: &'static str, + #[prop_or_default] + pub title: &'static str, + pub show_alert: UseStateHandle, + #[prop_or(Callback::noop())] + pub on_confirm: Callback<()>, + #[prop_or(Callback::noop())] + pub on_cancel: Callback<()>, + #[prop_or(5000)] + pub timeout: u32, + #[prop_or("button")] + pub cancel_button_class: &'static str, + #[prop_or("button")] + pub confirm_button_class: &'static str, + #[prop_or_default] + pub show_cancel_button: bool, + #[prop_or(true)] + pub show_confirm_button: bool, +} + +#[derive(Clone, PartialEq, Properties)] +pub struct AlertPropsAlternate { + #[prop_or_default] + pub counter: u32, + #[prop_or_default] + pub message: &'static str, + #[prop_or_default] + pub title: &'static str, + #[prop_or_default] + pub show_alert: bool, + #[prop_or(Callback::noop())] + pub on_confirm: Callback<()>, + #[prop_or(Callback::noop())] + pub on_cancel: Callback<()>, + #[prop_or(5000)] + pub timeout: u32, + #[prop_or("button")] + pub cancel_button_class: &'static str, + #[prop_or("button")] + pub confirm_button_class: &'static str, + #[prop_or_default] + pub show_cancel_button: bool, + #[prop_or(true)] + pub show_confirm_button: bool, +} diff --git a/sh-frontend/src/components/input/input_field.rs b/sh-frontend/src/components/input/input_field.rs index 4f79097..1c1ecdb 100644 --- a/sh-frontend/src/components/input/input_field.rs +++ b/sh-frontend/src/components/input/input_field.rs @@ -46,7 +46,6 @@ pub fn input_field(props: &InputFieldProps) -> Html { read_only, } = props; - log::debug!("InputField: {:?}", input_value); html! { <>