From aca374090df2d686ebb435a4ac510b6e5ef19a8a Mon Sep 17 00:00:00 2001 From: Shubhranshu Sanjeev Date: Tue, 12 Nov 2024 21:51:42 +0530 Subject: [PATCH] feat: breadcrumbs for easy navigation --- crates/frontend/src/app.rs | 231 +------------ crates/frontend/src/hoc.rs | 1 + crates/frontend/src/hoc/layout.rs | 121 +++---- crates/frontend/src/hoc/nav_breadcrums.rs | 325 ++++++++++++++++++ crates/frontend/src/lib.rs | 1 + crates/frontend/src/pages.rs | 6 +- crates/frontend/src/pages/context_override.rs | 3 +- ...w_custom_types.rs => new_template_type.rs} | 2 +- .../{custom_types.rs => template_types.rs} | 0 ...ustom_types.rs => update_template_type.rs} | 2 +- crates/frontend/src/routes.rs | 209 +++++++++++ 11 files changed, 600 insertions(+), 301 deletions(-) create mode 100644 crates/frontend/src/hoc/nav_breadcrums.rs rename crates/frontend/src/pages/{new_custom_types.rs => new_template_type.rs} (85%) rename crates/frontend/src/pages/{custom_types.rs => template_types.rs} (100%) rename crates/frontend/src/pages/{update_custom_types.rs => update_template_type.rs} (97%) create mode 100644 crates/frontend/src/routes.rs diff --git a/crates/frontend/src/app.rs b/crates/frontend/src/app.rs index dca4e892..719f81df 100755 --- a/crates/frontend/src/app.rs +++ b/crates/frontend/src/app.rs @@ -3,22 +3,8 @@ use leptos_meta::*; use leptos_router::*; use serde_json::json; -use crate::hoc::layout::Layout; -use crate::pages::config_version::ConfigVersion; -use crate::pages::config_version_list::ConfigVersionList; -use crate::pages::dimensions::Dimensions; -use crate::pages::experiment_list::ExperimentList; -use crate::pages::function::{ - function_create::CreateFunctionView, function_list::FunctionList, FunctionPage, -}; -use crate::pages::new_custom_types::NewCustomTypes; -use crate::pages::new_experiment::NewExperiment; -use crate::pages::update_custom_types::UpdateCustomTypes; -use crate::pages::{ - context_override::ContextOverride, custom_types::TypesPage, - default_config::DefaultConfig, experiment::ExperimentPage, home::Home, -}; use crate::providers::alert_provider::AlertProvider; +use crate::routes::AppRoutes; use crate::types::Envs; #[component] @@ -95,220 +81,7 @@ pub fn app(app_envs: Envs) -> impl IntoView { - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - - - - } - } - /> - - // - // - // - // } - // } - // /> + diff --git a/crates/frontend/src/hoc.rs b/crates/frontend/src/hoc.rs index dd646199..76b2d050 100644 --- a/crates/frontend/src/hoc.rs +++ b/crates/frontend/src/hoc.rs @@ -1 +1,2 @@ pub mod layout; +pub mod nav_breadcrums; diff --git a/crates/frontend/src/hoc/layout.rs b/crates/frontend/src/hoc/layout.rs index 764cb7f0..b8f8f3e5 100644 --- a/crates/frontend/src/hoc/layout.rs +++ b/crates/frontend/src/hoc/layout.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use crate::{ components::{side_nav::SideNav, toast::Toast}, + hoc::nav_breadcrums::{BreadcrumbCtx, Breadcrumbs}, providers::alert_provider::AlertQueue, utils::{use_tenants, use_url_base}, }; @@ -20,13 +21,17 @@ impl AsRef for PageHeading { } #[component] -pub fn Layout(children: Children) -> impl IntoView { - let route_ctx = use_route(); +pub fn Layout() -> impl IntoView { let params_map = use_params_map(); let tenant = Signal::derive(move || match params_map.get().get("tenant") { Some(tenant) => tenant.clone(), None => String::from("no-tenant"), }); + let breadcrumbs = create_rw_signal(BreadcrumbCtx { + current_path: String::new(), + params: ParamsMap::new() + }); + provide_context(breadcrumbs); provide_context(tenant); view! { @@ -34,70 +39,10 @@ pub fn Layout(children: Children) -> impl IntoView {
-
- - -
-
{children()}
+
+
+ +
{move || { @@ -112,3 +57,47 @@ pub fn Layout(children: Children) -> impl IntoView { } } + +#[component] +fn header(tenant: Signal) -> impl IntoView { + view! { +
+ + +
+ } +} diff --git a/crates/frontend/src/hoc/nav_breadcrums.rs b/crates/frontend/src/hoc/nav_breadcrums.rs new file mode 100644 index 00000000..36f543fe --- /dev/null +++ b/crates/frontend/src/hoc/nav_breadcrums.rs @@ -0,0 +1,325 @@ +use leptos::*; +use leptos_router::*; +use once_cell::sync::Lazy; +use std::{collections::HashMap, sync::Arc}; + +use crate::utils::use_url_base; + +// Types and Structures +#[derive(Clone)] +struct RouteMetadata { + default_label: String, + parent: Option, + icon: Option, + dynamic_label: Option, +} + +type DynamicLabelFn = Box Option + Send + Sync>>; + +#[derive(Clone)] +pub struct BreadcrumbCtx { + pub current_path: String, + pub params: ParamsMap, +} + +static BREADCRUMB_STORE: Lazy> = Lazy::new(|| { + let mut routes = HashMap::new(); + + // Dimensions + routes.insert( + "/dimensions".to_string(), + RouteMetadata { + default_label: "Dimensions".to_string(), + parent: None, + icon: Some("ri-ruler-2-fill".to_string()), + dynamic_label: None, + }, + ); + + // Functions + routes.insert( + "/function".to_string(), + RouteMetadata { + default_label: "Functions".to_string(), + parent: None, + icon: Some("ri-code-box-fill".to_string()), + dynamic_label: None, + }, + ); + + routes.insert( + "/function/create".to_string(), + RouteMetadata { + default_label: "Create Function".to_string(), + parent: Some("/function".to_string()), + icon: None, + dynamic_label: None, + }, + ); + + routes.insert( + "/function/:function_name".to_string(), + RouteMetadata { + default_label: "Function Details".to_string(), + parent: Some("/function".to_string()), + icon: None, + dynamic_label: Some(Box::new(Arc::new(|params| { + params.get("function_name").map(|name| name.to_string()) + }))), + }, + ); + + // Experiments + routes.insert( + "/experiments".to_string(), + RouteMetadata { + default_label: "Experiments".to_string(), + parent: None, + icon: Some("ri-test-tube-fill".to_string()), + dynamic_label: None, + }, + ); + + routes.insert( + "/experiments/new".to_string(), + RouteMetadata { + default_label: "New Experiment".to_string(), + parent: Some("/experiments".to_string()), + icon: None, + dynamic_label: None, + }, + ); + + routes.insert( + "/experiments/:id".to_string(), + RouteMetadata { + default_label: "Experiment Details".to_string(), + parent: Some("/experiments".to_string()), + icon: None, + dynamic_label: Some(Box::new(Arc::new(|params| { + params.get("id").map(|id| format!("{}", id)) + }))), + }, + ); + + // default-config + routes.insert( + "/default-config".to_string(), + RouteMetadata { + default_label: "Default Config".to_string(), + parent: None, + icon: Some("ri-tools-line".to_string()), + dynamic_label: None, + }, + ); + + // overrides + routes.insert( + "/overrides".to_string(), + RouteMetadata { + default_label: "Overrides".to_string(), + parent: None, + + icon: Some("ri-guide-fill".to_string()), + dynamic_label: None, + }, + ); + routes.insert( + "/resolve".to_string(), + RouteMetadata { + default_label: "Resolve".to_string(), + parent: None, + icon: Some("ri-equalizer-fill".to_string()), + dynamic_label: None, + }, + ); + + // type templates + routes.insert( + "/types".to_string(), + RouteMetadata { + default_label: "Types".to_string(), + parent: None, + icon: Some("ri-t-box-fill".to_string()), + dynamic_label: None, + }, + ); + + routes.insert( + "/types/new".to_string(), + RouteMetadata { + default_label: "New Type".to_string(), + parent: Some("/types".to_string()), + icon: None, + dynamic_label: None, + }, + ); + + routes.insert( + "/types/:type_name/update".to_string(), + RouteMetadata { + default_label: "Update Type".to_string(), + parent: Some("/types".to_string()), + icon: None, + dynamic_label: Some(Box::new(Arc::new(|params| { + params + .get("type_name") + .map(|name| format!("Update {}", name)) + }))), + }, + ); + + // config versions + routes.insert( + "/config/versions".to_string(), + RouteMetadata { + default_label: "Config Versions".to_string(), + parent: None, + icon: Some("ri-camera-lens-fill".to_string()), + dynamic_label: None, + }, + ); + routes.insert( + "/config/versions/:version".to_string(), + RouteMetadata { + default_label: "Config Versions Detail".to_string(), + parent: Some("/config/versions".to_string()), + icon: None, + dynamic_label: Some(Box::new(Arc::new(|params| { + params.get("version").map(|name| format!("{}", name)) + }))), + }, + ); + + routes +}); + +// Store Implementation +pub fn get_breadcrumbs( + current_path: &str, + params: &ParamsMap, +) -> Vec<(String, String, Option)> { + let mut breadcrumbs = Vec::new(); + let mut current = Some(current_path.to_string()); + + while let Some(path) = current { + if let Some(metadata) = BREADCRUMB_STORE.get(&path) { + // Replace path parameters with actual values + let mut resolved_path = path.clone(); + for (param_name, param_value) in params.0.clone() { + resolved_path = + resolved_path.replace(&format!(":{}", param_name), ¶m_value); + } + + // Get label using dynamic handler if available + let label = metadata + .dynamic_label + .as_ref() + .and_then(|handler| handler(params)) + .unwrap_or(metadata.default_label.clone()); + + breadcrumbs.push((resolved_path, label, metadata.icon.clone())); + current = metadata.parent.clone(); + } else { + break; + } + } + + breadcrumbs.reverse(); + breadcrumbs +} + +// Components +#[component] +pub fn with_breadcrumbs(children: Children) -> impl IntoView { + let params = use_params_map(); + let service_prefix = use_url_base(); + let breadcrumb_ctx = use_context::>() + .expect("BreadcrumbContext not found"); + + create_effect(move |_| { + let base = format!("{service_prefix}/admin/:tenant"); + let current_path = use_route().original_path().replace(&base, ""); + let params = params.get(); + logging::log!("{current_path} {}", use_route().original_path()); + logging::log!("{:?}", params.0); + breadcrumb_ctx.set(BreadcrumbCtx { + current_path, + params, + }); + }); + + children() +} + +#[component] +pub fn breadcrumbs() -> impl IntoView { + let breadcrumb_ctx = + use_context::>().expect("BreadcrumbStore not found"); + + let breadcrumbs = create_memo(move |_| { + let BreadcrumbCtx { + current_path, + params, + } = breadcrumb_ctx.get(); + logging::log!("BREAD {current_path}"); + logging::log!("BREAD {:?}", params.0); + get_breadcrumbs(¤t_path, ¶ms) + }); + + view! { + + } +} diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index 66bf2c42..1bf30d59 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -10,6 +10,7 @@ pub mod pages; pub mod providers; pub mod schema; pub mod types; +pub mod routes; mod utils; use cfg_if::cfg_if; diff --git a/crates/frontend/src/pages.rs b/crates/frontend/src/pages.rs index 55fa39a9..1e022c27 100644 --- a/crates/frontend/src/pages.rs +++ b/crates/frontend/src/pages.rs @@ -1,7 +1,7 @@ pub mod config_version; pub mod config_version_list; pub mod context_override; -pub mod custom_types; +pub mod template_types; pub mod default_config; pub mod dimensions; pub mod experiment; @@ -10,5 +10,5 @@ pub mod function; pub mod home; pub mod not_found; pub mod new_experiment; -pub mod new_custom_types; -pub mod update_custom_types; +pub mod new_template_type; +pub mod update_template_type; diff --git a/crates/frontend/src/pages/context_override.rs b/crates/frontend/src/pages/context_override.rs index 0ec72b47..90b4823a 100644 --- a/crates/frontend/src/pages/context_override.rs +++ b/crates/frontend/src/pages/context_override.rs @@ -132,7 +132,8 @@ fn form( #[component] pub fn context_override() -> impl IntoView { - let tenant_rs = use_context::>().unwrap(); + // TODO: check if all use_context try to get Signal + let tenant_rs = use_context::>().unwrap(); let (selected_context_rs, selected_context_ws) = create_signal::>(None); let (form_mode, set_form_mode) = create_signal::>(None); diff --git a/crates/frontend/src/pages/new_custom_types.rs b/crates/frontend/src/pages/new_template_type.rs similarity index 85% rename from crates/frontend/src/pages/new_custom_types.rs rename to crates/frontend/src/pages/new_template_type.rs index b41e40b1..3db18b62 100644 --- a/crates/frontend/src/pages/new_custom_types.rs +++ b/crates/frontend/src/pages/new_template_type.rs @@ -3,7 +3,7 @@ use leptos::*; use crate::components::type_template_form::TypeTemplateForm; #[component] -pub fn new_custom_types() -> impl IntoView { +pub fn new_template_type() -> impl IntoView { view! { impl IntoView { +pub fn update_template_type() -> impl IntoView { let tenant_rs = use_context::>().unwrap(); let path_params = use_params::(); let page_resource = create_blocking_resource( diff --git a/crates/frontend/src/routes.rs b/crates/frontend/src/routes.rs new file mode 100644 index 00000000..9313e347 --- /dev/null +++ b/crates/frontend/src/routes.rs @@ -0,0 +1,209 @@ +use leptos::*; +use leptos_router::{Route, SsrMode}; + +use crate::{ + hoc::{layout::Layout, nav_breadcrums::WithBreadcrumbs}, + pages::{ + config_version::ConfigVersion, + config_version_list::ConfigVersionList, + context_override::ContextOverride, + default_config::DefaultConfig, + dimensions::Dimensions, + experiment::ExperimentPage, + experiment_list::ExperimentList, + function::{ + function_create::CreateFunctionView, function_list::FunctionList, + FunctionPage, + }, + home::Home, + new_experiment::NewExperiment, + new_template_type::NewTemplateType, + template_types::TypesPage, + update_template_type::UpdateTemplateType, + }, +}; + +#[component(transparent)] +pub fn app_routes() -> impl IntoView { + view! { + } + } + > + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + } + } + /> + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + + + + } + } + /> + + } +}