diff --git a/Cargo.lock b/Cargo.lock index 591cc19..98477c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,12 +352,6 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" -[[package]] -name = "anymap" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" - [[package]] name = "arrayref" version = "0.3.6" @@ -660,12 +654,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg-match" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8100e46ff92eb85bf6dc2930c73f2a4f7176393c84a9446b3d501e1b354e7b34" - [[package]] name = "chrono" version = "0.4.23" @@ -1524,14 +1512,18 @@ checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "gloo" -version = "0.2.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ce6f2dfa9f57f15b848efa2aade5e1850dc72986b87a2b0752d44ca08f4967" +checksum = "23947965eee55e3e97a5cd142dd4c10631cc349b48cecca0ed230fd296f568cd" dependencies = [ - "gloo-console-timer", + "gloo-console", + "gloo-dialogs", "gloo-events", "gloo-file", + "gloo-render", + "gloo-storage", "gloo-timers", + "gloo-utils", ] [[package]] @@ -1548,11 +1540,12 @@ dependencies = [ ] [[package]] -name = "gloo-console-timer" -version = "0.1.0" +name = "gloo-dialogs" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b48675544b29ac03402c6dffc31a912f716e38d19f7e74b78b7e900ec3c941ea" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" dependencies = [ + "wasm-bindgen", "web-sys", ] @@ -1568,22 +1561,70 @@ dependencies = [ [[package]] name = "gloo-file" -version = "0.1.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9fecfe46b5dc3cc46f58e98ba580cc714f2c93860796d002eb3527a465ef49" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" dependencies = [ + "futures-channel", "gloo-events", "js-sys", "wasm-bindgen", "web-sys", ] +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-timers" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ + "futures-channel", + "futures-core", "js-sys", "wasm-bindgen", ] @@ -2258,19 +2299,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "lexical-core" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.139" @@ -2388,6 +2416,8 @@ dependencies = [ "base64 0.13.1", "chrono", "gloo-console", + "gloo-file", + "gloo-net", "graphql_client 0.10.0", "http", "image", @@ -2401,12 +2431,12 @@ dependencies = [ "validator", "validator_derive 0.16.0", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "yew", "yew-router", "yew_form", "yew_form_derive", - "yewtil", ] [[package]] @@ -2586,17 +2616,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "nom" version = "7.1.3" @@ -3287,6 +3306,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + [[package]] name = "rsa" version = "0.6.1" @@ -3411,6 +3436,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" + [[package]] name = "scopeguard" version = "1.1.0" @@ -3576,6 +3607,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7" +dependencies = [ + "fnv", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_bytes" version = "0.11.9" @@ -4480,8 +4523,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] @@ -4727,26 +4768,17 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yew" -version = "0.18.0" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d5154faef86dddd2eb333d4755ea5643787d20aca683e58759b0e53351409f" +checksum = "2a1ccb53e57d3f7d847338cf5758befa811cabe207df07f543c06f502f9998cd" dependencies = [ - "anyhow", - "anymap", - "bincode", - "cfg-if", - "cfg-match", "console_error_panic_hook", "gloo", - "http", + "gloo-utils", "indexmap", "js-sys", - "log", - "ryu", - "serde", - "serde_json", + "scoped-tls-hkt", "slab", - "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4755,12 +4787,13 @@ dependencies = [ [[package]] name = "yew-macro" -version = "0.18.0" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e23bfe3dc3933fbe9592d149c9985f3047d08c637a884b9344c21e56e092ef" +checksum = "5fab79082b556d768d6e21811869c761893f0450e1d550a67892b9bce303b7bb" dependencies = [ "boolinator", "lazy_static", + "proc-macro-error", "proc-macro2", "quote", "syn", @@ -4768,60 +4801,52 @@ dependencies = [ [[package]] name = "yew-router" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27666236d9597eac9be560e841e415e20ba67020bc8cd081076be178e159c8bc" +checksum = "155804f6f3aa309f596d5c3fa14486a94e7756f1edd7634569949e401d5099f2" dependencies = [ - "cfg-if", - "cfg-match", "gloo", + "gloo-utils", "js-sys", - "log", - "nom 5.1.2", + "route-recognizer", "serde", - "serde_json", + "serde-wasm-bindgen", + "serde_urlencoded", + "thiserror", "wasm-bindgen", "web-sys", "yew", "yew-router-macro", - "yew-router-route-parser", ] [[package]] name = "yew-router-macro" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0ace2924b7a175e2d1c0e62ee7022a5ad840040dcd52414ce5f410ab322dba" +checksum = "39049d193b52eaad4ffc80916bf08806d142c90b5edcebd527644de438a7e19a" dependencies = [ "proc-macro2", "quote", "syn", - "yew-router-route-parser", -] - -[[package]] -name = "yew-router-route-parser" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4a67208fb46b900af18a7397938b01f379dfc18da34799cfa8347eec715697" -dependencies = [ - "nom 5.1.2", ] [[package]] name = "yew_form" version = "0.1.8" -source = "git+https://github.com/jfbilodeau/yew_form?rev=67050812695b7a8a90b81b0637e347fc6629daed#67050812695b7a8a90b81b0637e347fc6629daed" +source = "git+https://github.com/jfbilodeau/yew_form?rev=4b9fabffb63393ec7626a4477fd36de12a07fac9#4b9fabffb63393ec7626a4477fd36de12a07fac9" dependencies = [ + "gloo-console", "validator", "validator_derive 0.14.0", + "wasm-bindgen", + "web-sys", "yew", ] [[package]] name = "yew_form_derive" version = "0.1.8" -source = "git+https://github.com/jfbilodeau/yew_form?rev=67050812695b7a8a90b81b0637e347fc6629daed#67050812695b7a8a90b81b0637e347fc6629daed" +source = "git+https://github.com/jfbilodeau/yew_form?rev=4b9fabffb63393ec7626a4477fd36de12a07fac9#4b9fabffb63393ec7626a4477fd36de12a07fac9" dependencies = [ "proc-macro2", "quote", @@ -4829,19 +4854,6 @@ dependencies = [ "yew_form", ] -[[package]] -name = "yewtil" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8543663ac49cd613df079282a1d8bdbdebdad6e02bac229f870fd4237b5d9aaa" -dependencies = [ - "log", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yew", -] - [[package]] name = "zeroize" version = "1.5.7" diff --git a/app/Cargo.toml b/app/Cargo.toml index c5573f1..cc489be 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -9,22 +9,24 @@ include = ["src/**/*", "queries/**/*", "Cargo.toml", "../schema.graphql"] anyhow = "1" base64 = "0.13" gloo-console = "0.2.3" +gloo-file = "0.2.3" +gloo-net = "*" graphql_client = "0.10" http = "0.2" jwt = "0.13" rand = "0.8" serde = "1" serde_json = "1" +url-escape = "0.1.1" validator = "=0.14" validator_derive = "*" wasm-bindgen = "0.2" -yew = "0.18" -yewtil = "*" -yew-router = "0.15" +wasm-bindgen-futures = "*" +yew = "0.19.3" +yew-router = "0.16" # Needed because of https://github.com/tkaitchuck/aHash/issues/95 indexmap = "=1.6.2" -url-escape = "0.1.1" [dependencies.web-sys] version = "0.3" @@ -57,11 +59,11 @@ version = "0.24" [dependencies.yew_form] git = "https://github.com/jfbilodeau/yew_form" -rev = "67050812695b7a8a90b81b0637e347fc6629daed" +rev = "4b9fabffb63393ec7626a4477fd36de12a07fac9" [dependencies.yew_form_derive] git = "https://github.com/jfbilodeau/yew_form" -rev = "67050812695b7a8a90b81b0637e347fc6629daed" +rev = "4b9fabffb63393ec7626a4477fd36de12a07fac9" [lib] crate-type = ["cdylib"] diff --git a/app/src/components/add_group_member.rs b/app/src/components/add_group_member.rs index 0ac4e23..79c056c 100644 --- a/app/src/components/add_group_member.rs +++ b/app/src/components/add_group_member.rs @@ -52,23 +52,25 @@ pub struct Props { } impl CommonComponent for AddGroupMemberComponent { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { match msg { Msg::UserListResponse(response) => { self.user_list = Some(response?.users); - self.common.cancel_task(); } - Msg::SubmitAddMember => return self.submit_add_member(), + Msg::SubmitAddMember => return self.submit_add_member(ctx), Msg::AddMemberResponse(response) => { response?; - self.common.cancel_task(); let user = self .selected_user .as_ref() .expect("Could not get selected user") .clone(); // Remove the user from the dropdown. - self.common.on_user_added_to_group.emit(user); + ctx.props().on_user_added_to_group.emit(user); } Msg::SelectionChanged(option_props) => { let was_some = self.selected_user.is_some(); @@ -88,23 +90,25 @@ impl CommonComponent for AddGroupMemberComponent { } impl AddGroupMemberComponent { - fn get_user_list(&mut self) { + fn get_user_list(&mut self, ctx: &Context) { self.common.call_graphql::( + ctx, list_user_names::Variables { filters: None }, Msg::UserListResponse, "Error trying to fetch user list", ); } - fn submit_add_member(&mut self) -> Result { + fn submit_add_member(&mut self, ctx: &Context) -> Result { let user_id = match self.selected_user.clone() { None => return Ok(false), Some(user) => user.id, }; self.common.call_graphql::( + ctx, add_user_to_group::Variables { user: user_id, - group: self.common.group_id, + group: ctx.props().group_id, }, Msg::AddMemberResponse, "Error trying to initiate adding the user to a group", @@ -112,8 +116,8 @@ impl AddGroupMemberComponent { Ok(true) } - fn get_selectable_user_list(&self, user_list: &[User]) -> Vec { - let user_groups = self.common.users.iter().collect::>(); + fn get_selectable_user_list(&self, ctx: &Context, user_list: &[User]) -> Vec { + let user_groups = ctx.props().users.iter().collect::>(); user_list .iter() .filter(|u| !user_groups.contains(u)) @@ -126,32 +130,29 @@ impl Component for AddGroupMemberComponent { type Message = Msg; type Properties = Props; - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(ctx: &Context) -> Self { let mut res = Self { - common: CommonComponentParts::::create(props, link), + common: CommonComponentParts::::create(), user_list: None, selected_user: None, }; - res.get_user_list(); + res.get_user_list(ctx); res } - fn update(&mut self, msg: Self::Message) -> ShouldRender { + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { CommonComponentParts::::update_and_report_error( self, + ctx, msg, - self.common.on_error.clone(), + ctx.props().on_error.clone(), ) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { - let link = &self.common; + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); if let Some(user_list) = &self.user_list { - let to_add_user_list = self.get_selectable_user_list(user_list); + let to_add_user_list = self.get_selectable_user_list(ctx, user_list); #[allow(unused_braces)] let make_select_option = |user: User| { html_nested! { diff --git a/app/src/components/add_user_to_group.rs b/app/src/components/add_user_to_group.rs index 1130ffb..7e8ce81 100644 --- a/app/src/components/add_user_to_group.rs +++ b/app/src/components/add_user_to_group.rs @@ -64,16 +64,18 @@ pub struct Props { } impl CommonComponent for AddUserToGroupComponent { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { match msg { Msg::GroupListResponse(response) => { self.group_list = Some(response?.groups.into_iter().map(Into::into).collect()); - self.common.cancel_task(); } - Msg::SubmitAddGroup => return self.submit_add_group(), + Msg::SubmitAddGroup => return self.submit_add_group(ctx), Msg::AddGroupResponse(response) => { response?; - self.common.cancel_task(); // Adding the user to the group succeeded, we're not in the process of adding a // group anymore. let group = self @@ -82,7 +84,7 @@ impl CommonComponent for AddUserToGroupComponent { .expect("Could not get selected group") .clone(); // Remove the group from the dropdown. - self.common.on_user_added_to_group.emit(group); + ctx.props().on_user_added_to_group.emit(group); } Msg::SelectionChanged(option_props) => { let was_some = self.selected_group.is_some(); @@ -102,22 +104,24 @@ impl CommonComponent for AddUserToGroupComponent { } impl AddUserToGroupComponent { - fn get_group_list(&mut self) { + fn get_group_list(&mut self, ctx: &Context) { self.common.call_graphql::( + ctx, get_group_list::Variables, Msg::GroupListResponse, "Error trying to fetch group list", ); } - fn submit_add_group(&mut self) -> Result { + fn submit_add_group(&mut self, ctx: &Context) -> Result { let group_id = match &self.selected_group { None => return Ok(false), Some(group) => group.id, }; self.common.call_graphql::( + ctx, add_user_to_group::Variables { - user: self.common.username.clone(), + user: ctx.props().username.clone(), group: group_id, }, Msg::AddGroupResponse, @@ -126,8 +130,8 @@ impl AddUserToGroupComponent { Ok(true) } - fn get_selectable_group_list(&self, group_list: &[Group]) -> Vec { - let user_groups = self.common.groups.iter().collect::>(); + fn get_selectable_group_list(&self, props: &Props, group_list: &[Group]) -> Vec { + let user_groups = props.groups.iter().collect::>(); group_list .iter() .filter(|g| !user_groups.contains(g)) @@ -139,32 +143,29 @@ impl AddUserToGroupComponent { impl Component for AddUserToGroupComponent { type Message = Msg; type Properties = Props; - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(ctx: &Context) -> Self { let mut res = Self { - common: CommonComponentParts::::create(props, link), + common: CommonComponentParts::::create(), group_list: None, selected_group: None, }; - res.get_group_list(); + res.get_group_list(ctx); res } - fn update(&mut self, msg: Self::Message) -> ShouldRender { + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { CommonComponentParts::::update_and_report_error( self, + ctx, msg, - self.common.on_error.clone(), + ctx.props().on_error.clone(), ) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { - let link = &self.common; + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); if let Some(group_list) = &self.group_list { - let to_add_group_list = self.get_selectable_group_list(group_list); + let to_add_group_list = self.get_selectable_group_list(ctx.props(), group_list); #[allow(unused_braces)] let make_select_option = |group: Group| { html_nested! { diff --git a/app/src/components/app.rs b/app/src/components/app.rs index ed0d387..d2d48fb 100644 --- a/app/src/components/app.rs +++ b/app/src/components/app.rs @@ -9,7 +9,7 @@ use crate::{ logout::LogoutButton, reset_password_step1::ResetPasswordStep1Form, reset_password_step2::ResetPasswordStep2Form, - router::{AppRoute, Link, NavButton}, + router::{AppRoute, Link, Redirect}, user_details::UserDetails, user_table::UserTable, }, @@ -17,21 +17,31 @@ use crate::{ }; use gloo_console::error; -use yew::{prelude::*, services::fetch::FetchTask}; +use yew::{ + function_component, + html::Scope, + prelude::{html, Component, Html}, + Context, +}; use yew_router::{ - agent::{RouteAgentDispatcher, RouteRequest}, - route::Route, - router::Router, - service::RouteService, + prelude::{History, Location}, + scope_ext::RouterScopeExt, + BrowserRouter, Switch, }; +#[function_component(AppContainer)] +pub fn app_container() -> Html { + html! { + + + + } +} + pub struct App { - link: ComponentLink, user_info: Option<(String, bool)>, redirect_to: Option, - route_dispatcher: RouteAgentDispatcher, password_reset_enabled: Option, - task: Option, } pub enum Msg { @@ -44,9 +54,8 @@ impl Component for App { type Message = Msg; type Properties = (); - fn create(_: Self::Properties, link: ComponentLink) -> Self { - let mut app = Self { - link, + fn create(ctx: &Context) -> Self { + let app = Self { user_info: get_cookie("user_id") .unwrap_or_else(|e| { error!(&e.to_string()); @@ -60,48 +69,42 @@ impl Component for App { None }) }), - redirect_to: Self::get_redirect_route(), - route_dispatcher: RouteAgentDispatcher::new(), + redirect_to: Self::get_redirect_route(ctx), password_reset_enabled: None, - task: None, }; - app.task = Some( - HostService::probe_password_reset( - app.link.callback_once(Msg::PasswordResetProbeFinished), - ) - .unwrap(), - ); - app.apply_initial_redirections(); + let link = ctx.link().clone(); + wasm_bindgen_futures::spawn_local(async move { + let result = HostService::probe_password_reset().await; + link.send_message(Msg::PasswordResetProbeFinished(result)); + }); + app.apply_initial_redirections(ctx); app } - fn update(&mut self, msg: Self::Message) -> ShouldRender { + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + let history = ctx.link().history().unwrap(); match msg { Msg::Login((user_name, is_admin)) => { self.user_info = Some((user_name.clone(), is_admin)); - self.route_dispatcher - .send(RouteRequest::ChangeRoute(Route::from( - self.redirect_to.take().unwrap_or_else(|| { - if is_admin { - AppRoute::ListUsers - } else { - AppRoute::UserDetails(user_name.clone()) - } - }), - ))); + history.push(self.redirect_to.take().unwrap_or_else(|| { + if is_admin { + AppRoute::ListUsers + } else { + AppRoute::UserDetails { + user_id: user_name.clone(), + } + } + })); } Msg::Logout => { self.user_info = None; self.redirect_to = None; - self.route_dispatcher - .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login))); + history.push(AppRoute::Login); } Msg::PasswordResetProbeFinished(Ok(enabled)) => { - self.task = None; self.password_reset_enabled = Some(enabled); } Msg::PasswordResetProbeFinished(Err(err)) => { - self.task = None; self.password_reset_enabled = Some(false); error!(&format!( "Could not probe for password reset support: {err:#}" @@ -111,24 +114,20 @@ impl Component for App { true } - fn change(&mut self, _: Self::Properties) -> ShouldRender { - false - } - - fn view(&self) -> Html { - let link = self.link.clone(); + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link().clone(); let is_admin = self.is_admin(); let password_reset_enabled = self.password_reset_enabled; html! {
- {self.view_banner()} + {self.view_banner(ctx)}
-
- - render={Router::render(move |s| Self::dispatch_route(s, &link, is_admin, password_reset_enabled))} +
+ + render={Switch::render(move |routes| Self::dispatch_route(routes, &link, is_admin, password_reset_enabled))} /> -
+
{self.view_footer()}
@@ -138,59 +137,56 @@ impl Component for App { } impl App { - fn get_redirect_route() -> Option { - let route_service = RouteService::<()>::new(); - let current_route = route_service.get_path(); - if current_route.is_empty() - || current_route == "/" - || current_route.contains("login") - || current_route.contains("reset-password") - { - None - } else { - use yew_router::Switch; - AppRoute::from_route_part::<()>(current_route, None).0 - } + fn get_redirect_route(ctx: &Context) -> Option { + let history = ctx.link().history().unwrap(); + let route = history.location().route::(); + route.and_then(|route| match route { + AppRoute::Index + | AppRoute::Login + | AppRoute::StartResetPassword + | AppRoute::FinishResetPassword { token: _ } => None, + _ => Some(route), + }) } - fn apply_initial_redirections(&mut self) { - let route_service = RouteService::<()>::new(); - let current_route = route_service.get_path(); - if current_route.contains("reset-password") { - if self.password_reset_enabled == Some(false) { - self.route_dispatcher - .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login))); - } - return; - } - match &self.user_info { - None => { - self.route_dispatcher - .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::Login))); - } - Some((user_name, is_admin)) => match &self.redirect_to { - Some(url) => { - self.route_dispatcher - .send(RouteRequest::ReplaceRoute(Route::from(url.clone()))); + fn apply_initial_redirections(&self, ctx: &Context) { + let history = ctx.link().history().unwrap(); + let route = history.location().route::(); + let redirection = if let Some(route) = route { + if matches!( + route, + AppRoute::StartResetPassword | AppRoute::FinishResetPassword { token: _ } + ) && self.password_reset_enabled == Some(false) + { + Some(AppRoute::Login) + } else { + match &self.user_info { + None => Some(AppRoute::Login), + Some((user_name, is_admin)) => match &self.redirect_to { + Some(url) => Some(url.clone()), + None => { + if *is_admin { + Some(AppRoute::ListUsers) + } else { + Some(AppRoute::UserDetails { + user_id: user_name.clone(), + }) + } + } + }, } - None => { - if *is_admin { - self.route_dispatcher - .send(RouteRequest::ReplaceRoute(Route::from(AppRoute::ListUsers))); - } else { - self.route_dispatcher - .send(RouteRequest::ReplaceRoute(Route::from( - AppRoute::UserDetails(user_name.clone()), - ))); - } - } - }, + } + } else { + Some(AppRoute::Login) + }; + if let Some(redirect_to) = redirection { + history.push(redirect_to); } } fn dispatch_route( - switch: AppRoute, - link: &ComponentLink, + switch: &AppRoute, + link: &Scope, is_admin: bool, password_reset_enabled: Option, ) -> Html { @@ -204,10 +200,10 @@ impl App { AppRoute::Index | AppRoute::ListUsers => html! {
- + {"Create a user"} - +
}, AppRoute::CreateGroup => html! { @@ -216,41 +212,41 @@ impl App { AppRoute::ListGroups => html! {
- + {"Create a group"} - +
}, - AppRoute::GroupDetails(group_id) => html! { - + AppRoute::GroupDetails { group_id } => html! { + }, - AppRoute::UserDetails(username) => html! { - + AppRoute::UserDetails { user_id } => html! { + }, - AppRoute::ChangePassword(username) => html! { - + AppRoute::ChangePassword { user_id } => html! { + }, AppRoute::StartResetPassword => match password_reset_enabled { Some(true) => html! { }, Some(false) => { - App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled) + html! { } } None => html! {}, }, - AppRoute::FinishResetPassword(token) => match password_reset_enabled { - Some(true) => html! { }, + AppRoute::FinishResetPassword { token } => match password_reset_enabled { + Some(true) => html! { }, Some(false) => { - App::dispatch_route(AppRoute::Login, link, is_admin, password_reset_enabled) + html! { } } None => html! {}, }, } } - fn view_banner(&self) -> Html { - let link = &self.link; + fn view_banner(&self, ctx: &Context) -> Html { + let link = ctx.link(); html! {
@@ -265,7 +261,7 @@ impl App {
  • + to={AppRoute::ListUsers}> {"Users"} @@ -273,7 +269,7 @@ impl App {
  • + to={AppRoute::ListGroups}> {"Groups"} @@ -311,7 +307,7 @@ impl App {
  • + to={AppRoute::UserDetails{ user_id: user_id.clone() }}> {"View details"}
  • diff --git a/app/src/components/change_password.rs b/app/src/components/change_password.rs index 1e65046..d6d59d3 100644 --- a/app/src/components/change_password.rs +++ b/app/src/components/change_password.rs @@ -1,21 +1,18 @@ use crate::{ - components::router::{AppRoute, NavButton}, + components::router::{AppRoute, Link}, infra::{ api::HostService, common_component::{CommonComponent, CommonComponentParts}, }, }; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{anyhow, bail, Result}; use gloo_console::error; use lldap_auth::*; use validator_derive::Validate; use yew::prelude::*; use yew_form::Form; use yew_form_derive::Model; -use yew_router::{ - agent::{RouteAgentDispatcher, RouteRequest}, - route::Route, -}; +use yew_router::{prelude::History, scope_ext::RouterScopeExt}; #[derive(PartialEq, Eq, Default)] enum OpaqueData { @@ -57,7 +54,6 @@ pub struct ChangePasswordForm { common: CommonComponentParts, form: Form, opaque_data: OpaqueData, - route_dispatcher: RouteAgentDispatcher, } #[derive(Clone, PartialEq, Eq, Properties)] @@ -76,15 +72,20 @@ pub enum Msg { } impl CommonComponent for ChangePasswordForm { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { + use anyhow::Context; match msg { Msg::FormUpdate => Ok(true), Msg::Submit => { if !self.form.validate() { bail!("Check the form for errors"); } - if self.common.is_admin { - self.handle_msg(Msg::SubmitNewPassword) + if ctx.props().is_admin { + self.handle_msg(ctx, Msg::SubmitNewPassword) } else { let old_password = self.form.model().old_password; if old_password.is_empty() { @@ -96,14 +97,14 @@ impl CommonComponent for ChangePasswordForm { .context("Could not initialize login")?; self.opaque_data = OpaqueData::Login(login_start_request.state); let req = login::ClientLoginStartRequest { - username: self.common.username.clone(), + username: ctx.props().username.clone(), login_start_request: login_start_request.message, }; self.common.call_backend( - HostService::login_start, - req, + ctx, + HostService::login_start(req), Msg::AuthenticationStartResponse, - )?; + ); Ok(true) } } @@ -122,7 +123,7 @@ impl CommonComponent for ChangePasswordForm { } _ => panic!("Unexpected data in opaque_data field"), }; - self.handle_msg(Msg::SubmitNewPassword) + self.handle_msg(ctx, Msg::SubmitNewPassword) } Msg::SubmitNewPassword => { let mut rng = rand::rngs::OsRng; @@ -131,15 +132,15 @@ impl CommonComponent for ChangePasswordForm { opaque::client::registration::start_registration(&new_password, &mut rng) .context("Could not initiate password change")?; let req = registration::ClientRegistrationStartRequest { - username: self.common.username.clone(), + username: ctx.props().username.clone(), registration_start_request: registration_start_request.message, }; self.opaque_data = OpaqueData::Registration(registration_start_request.state); self.common.call_backend( - HostService::register_start, - req, + ctx, + HostService::register_start(req), Msg::RegistrationStartResponse, - )?; + ); Ok(true) } Msg::RegistrationStartResponse(res) => { @@ -159,22 +160,20 @@ impl CommonComponent for ChangePasswordForm { registration_upload: registration_finish.message, }; self.common.call_backend( - HostService::register_finish, - req, + ctx, + HostService::register_finish(req), Msg::RegistrationFinishResponse, - ) + ); } _ => panic!("Unexpected data in opaque_data field"), - }?; + }; Ok(false) } Msg::RegistrationFinishResponse(response) => { - self.common.cancel_task(); if response.is_ok() { - self.route_dispatcher - .send(RouteRequest::ChangeRoute(Route::from( - AppRoute::UserDetails(self.common.username.clone()), - ))); + ctx.link().history().unwrap().push(AppRoute::UserDetails { + user_id: ctx.props().username.clone(), + }); } response?; Ok(true) @@ -191,26 +190,21 @@ impl Component for ChangePasswordForm { type Message = Msg; type Properties = Props; - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(_: &Context) -> Self { ChangePasswordForm { - common: CommonComponentParts::::create(props, link), + common: CommonComponentParts::::create(), form: yew_form::Form::::new(FormModel::default()), opaque_data: OpaqueData::None, - route_dispatcher: RouteAgentDispatcher::new(), } } - fn update(&mut self, msg: Self::Message) -> ShouldRender { - CommonComponentParts::::update(self, msg) + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + CommonComponentParts::::update(self, ctx, msg) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { - let is_admin = self.common.is_admin; - let link = &self.common; + fn view(&self, ctx: &Context) -> Html { + let is_admin = ctx.props().is_admin; + let link = ctx.link(); type Field = yew_form::Field; html! { <> @@ -305,12 +299,12 @@ impl Component for ChangePasswordForm { {"Save changes"} - + to={AppRoute::UserDetails{user_id: ctx.props().username.clone()}}> {"Back"} - +
    diff --git a/app/src/components/create_group.rs b/app/src/components/create_group.rs index c1b5f05..a7e6641 100644 --- a/app/src/components/create_group.rs +++ b/app/src/components/create_group.rs @@ -8,10 +8,7 @@ use graphql_client::GraphQLQuery; use validator_derive::Validate; use yew::prelude::*; use yew_form_derive::Model; -use yew_router::{ - agent::{RouteAgentDispatcher, RouteRequest}, - route::Route, -}; +use yew_router::{prelude::History, scope_ext::RouterScopeExt}; #[derive(GraphQLQuery)] #[graphql( @@ -24,7 +21,6 @@ pub struct CreateGroup; pub struct CreateGroupForm { common: CommonComponentParts, - route_dispatcher: RouteAgentDispatcher, form: yew_form::Form, } @@ -41,7 +37,11 @@ pub enum Msg { } impl CommonComponent for CreateGroupForm { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { match msg { Msg::Update => Ok(true), Msg::SubmitForm => { @@ -53,6 +53,7 @@ impl CommonComponent for CreateGroupForm { name: model.groupname, }; self.common.call_graphql::( + ctx, req, Msg::CreateGroupResponse, "Error trying to create group", @@ -64,8 +65,7 @@ impl CommonComponent for CreateGroupForm { "Created group '{}'", &response?.create_group.display_name )); - self.route_dispatcher - .send(RouteRequest::ChangeRoute(Route::from(AppRoute::ListGroups))); + ctx.link().history().unwrap().push(AppRoute::ListGroups); Ok(true) } } @@ -80,24 +80,19 @@ impl Component for CreateGroupForm { type Message = Msg; type Properties = (); - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(_: &Context) -> Self { Self { - common: CommonComponentParts::::create(props, link), - route_dispatcher: RouteAgentDispatcher::new(), + common: CommonComponentParts::::create(), form: yew_form::Form::::new(CreateGroupModel::default()), } } - fn update(&mut self, msg: Self::Message) -> ShouldRender { - CommonComponentParts::::update(self, msg) + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + CommonComponentParts::::update(self, ctx, msg) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { - let link = &self.common; + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); type Field = yew_form::Field; html! {
    diff --git a/app/src/components/create_user.rs b/app/src/components/create_user.rs index 7ee0fab..34de174 100644 --- a/app/src/components/create_user.rs +++ b/app/src/components/create_user.rs @@ -5,17 +5,14 @@ use crate::{ common_component::{CommonComponent, CommonComponentParts}, }, }; -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use gloo_console::log; use graphql_client::GraphQLQuery; use lldap_auth::{opaque, registration}; use validator_derive::Validate; use yew::prelude::*; use yew_form_derive::Model; -use yew_router::{ - agent::{RouteAgentDispatcher, RouteRequest}, - route::Route, -}; +use yew_router::{prelude::History, scope_ext::RouterScopeExt}; #[derive(GraphQLQuery)] #[graphql( @@ -28,7 +25,6 @@ pub struct CreateUser; pub struct CreateUserForm { common: CommonComponentParts, - route_dispatcher: RouteAgentDispatcher, form: yew_form::Form, } @@ -73,7 +69,11 @@ pub enum Msg { } impl CommonComponent for CreateUserForm { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { match msg { Msg::Update => Ok(true), Msg::SubmitForm => { @@ -93,6 +93,7 @@ impl CommonComponent for CreateUserForm { }, }; self.common.call_graphql::( + ctx, req, Msg::CreateUserResponse, "Error trying to create user", @@ -122,12 +123,11 @@ impl CommonComponent for CreateUserForm { registration_start_request: message, }; self.common - .call_backend(HostService::register_start, req, move |r| { + .call_backend(ctx, HostService::register_start(req), move |r| { Msg::RegistrationStartResponse((state, r)) - }) - .context("Error trying to create user")?; + }); } else { - self.update(Msg::SuccessfulCreation); + self.update(ctx, Msg::SuccessfulCreation); } Ok(false) } @@ -143,22 +143,19 @@ impl CommonComponent for CreateUserForm { server_data: response.server_data, registration_upload: registration_upload.message, }; - self.common - .call_backend( - HostService::register_finish, - req, - Msg::RegistrationFinishResponse, - ) - .context("Error trying to register user")?; + self.common.call_backend( + ctx, + HostService::register_finish(req), + Msg::RegistrationFinishResponse, + ); Ok(false) } Msg::RegistrationFinishResponse(response) => { response?; - self.handle_msg(Msg::SuccessfulCreation) + self.handle_msg(ctx, Msg::SuccessfulCreation) } Msg::SuccessfulCreation => { - self.route_dispatcher - .send(RouteRequest::ChangeRoute(Route::from(AppRoute::ListUsers))); + ctx.link().history().unwrap().push(AppRoute::ListUsers); Ok(true) } } @@ -173,24 +170,19 @@ impl Component for CreateUserForm { type Message = Msg; type Properties = (); - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(_: &Context) -> Self { Self { - common: CommonComponentParts::::create(props, link), - route_dispatcher: RouteAgentDispatcher::new(), + common: CommonComponentParts::::create(), form: yew_form::Form::::new(CreateUserModel::default()), } } - fn update(&mut self, msg: Self::Message) -> ShouldRender { - CommonComponentParts::::update(self, msg) + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + CommonComponentParts::::update(self, ctx, msg) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { - let link = &self.common; + fn view(&self, ctx: &Context) -> Html { + let link = &ctx.link(); type Field = yew_form::Field; html! {
    diff --git a/app/src/components/delete_group.rs b/app/src/components/delete_group.rs index 0934976..1fe3ce9 100644 --- a/app/src/components/delete_group.rs +++ b/app/src/components/delete_group.rs @@ -39,16 +39,21 @@ pub enum Msg { } impl CommonComponent for DeleteGroup { - fn handle_msg(&mut self, msg: ::Message) -> Result { + fn handle_msg( + &mut self, + ctx: &Context, + msg: ::Message, + ) -> Result { match msg { Msg::ClickedDeleteGroup => { self.modal.as_ref().expect("modal not initialized").show(); } Msg::ConfirmDeleteGroup => { - self.update(Msg::DismissModal); + self.update(ctx, Msg::DismissModal); self.common.call_graphql::( + ctx, delete_group_query::Variables { - group_id: self.common.group.id, + group_id: ctx.props().group.id, }, Msg::DeleteGroupResponse, "Error trying to delete group", @@ -58,12 +63,8 @@ impl CommonComponent for DeleteGroup { self.modal.as_ref().expect("modal not initialized").hide(); } Msg::DeleteGroupResponse(response) => { - self.common.cancel_task(); response?; - self.common - .props - .on_group_deleted - .emit(self.common.group.id); + ctx.props().on_group_deleted.emit(ctx.props().group.id); } } Ok(true) @@ -78,15 +79,15 @@ impl Component for DeleteGroup { type Message = Msg; type Properties = DeleteGroupProps; - fn create(props: Self::Properties, link: ComponentLink) -> Self { + fn create(_: &Context) -> Self { Self { - common: CommonComponentParts::::create(props, link), + common: CommonComponentParts::::create(), node_ref: NodeRef::default(), modal: None, } } - fn rendered(&mut self, first_render: bool) { + fn rendered(&mut self, _: &Context, first_render: bool) { if first_render { self.modal = Some(Modal::new( self.node_ref @@ -96,20 +97,17 @@ impl Component for DeleteGroup { } } - fn update(&mut self, msg: Self::Message) -> ShouldRender { + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { CommonComponentParts::::update_and_report_error( self, + ctx, msg, - self.common.on_error.clone(), + ctx.props().on_error.clone(), ) } - fn change(&mut self, props: Self::Properties) -> ShouldRender { - self.common.change(props) - } - - fn view(&self) -> Html { - let link = &self.common; + fn view(&self, ctx: &Context) -> Html { + let link = &ctx.link(); html! { <> - {self.show_modal()} + {self.show_modal(ctx)} } } } impl DeleteGroup { - fn show_modal(&self) -> Html { - let link = &self.common; + fn show_modal(&self, ctx: &Context) -> Html { + let link = &ctx.link(); html! {