[pdm-devel] [PATCH datacenter-manager 4/7] ui: add possibility to insert into search box

Dominik Csapak d.csapak at proxmox.com
Wed Apr 16 13:49:22 CEST 2025


by implementing a 'SearchProvider' context. This enables us to
insert a search term from everywhere. This can be helpful e.g. if we
want to prefill the search box with a specific pattern

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 ui/Cargo.toml               |  1 +
 ui/src/lib.rs               |  3 +++
 ui/src/main.rs              | 17 ++++++++++++-----
 ui/src/search_provider.rs   | 35 +++++++++++++++++++++++++++++++++++
 ui/src/widget/search_box.rs | 26 +++++++++++++++++++++-----
 5 files changed, 72 insertions(+), 10 deletions(-)
 create mode 100644 ui/src/search_provider.rs

diff --git a/ui/Cargo.toml b/ui/Cargo.toml
index 10345a2..96fd07e 100644
--- a/ui/Cargo.toml
+++ b/ui/Cargo.toml
@@ -42,6 +42,7 @@ pbs-api-types = "0.2.0"
 pdm-api-types = { version = "0.1", path = "../lib/pdm-api-types" }
 pdm-ui-shared = { version = "0.1", path = "../lib/pdm-ui-shared" }
 pdm-client = { version = "0.1", path = "../lib/pdm-client" }
+pdm-search = { version = "0.1", path = "../lib/pdm-search" }
 
 [patch.crates-io]
 # proxmox-client = { path = "../../proxmox/proxmox-client" }
diff --git a/ui/src/lib.rs b/ui/src/lib.rs
index e3755ec..edb50f9 100644
--- a/ui/src/lib.rs
+++ b/ui/src/lib.rs
@@ -21,6 +21,9 @@ pub use remotes::RemoteConfigPanel;
 mod top_nav_bar;
 pub use top_nav_bar::TopNavBar;
 
+mod search_provider;
+pub use search_provider::SearchProvider;
+
 mod dashboard;
 pub use dashboard::Dashboard;
 use yew_router::prelude::RouterScopeExt;
diff --git a/ui/src/main.rs b/ui/src/main.rs
index 6e2c9b2..be0c10c 100644
--- a/ui/src/main.rs
+++ b/ui/src/main.rs
@@ -22,7 +22,7 @@ use proxmox_yew_comp::{
 
 //use pbs::MainMenu;
 use pdm_api_types::subscription::{RemoteSubscriptionState, RemoteSubscriptions};
-use pdm_ui::{register_pve_tasks, MainMenu, RemoteList, TopNavBar};
+use pdm_ui::{register_pve_tasks, MainMenu, RemoteList, SearchProvider, TopNavBar};
 
 type MsgRemoteList = Result<RemoteList, Error>;
 
@@ -46,6 +46,7 @@ struct DatacenterManagerApp {
     remote_list: RemoteList,
     remote_list_error: Option<String>,
     remote_list_timeout: Option<Timeout>,
+    search_provider: SearchProvider,
 }
 
 async fn check_subscription() -> Msg {
@@ -166,6 +167,7 @@ impl Component for DatacenterManagerApp {
             remote_list: Vec::new().into(),
             remote_list_error: None,
             remote_list_timeout: None,
+            search_provider: SearchProvider::new(),
         };
 
         this.on_login(ctx, false);
@@ -258,10 +260,15 @@ impl Component for DatacenterManagerApp {
             .with_optional_child(subscription_alert);
 
         let context = self.remote_list.clone();
-
-        DesktopApp::new(
-            html! {<ContextProvider<RemoteList> {context}>{body}</ContextProvider<RemoteList>>},
-        )
+        let search_context = self.search_provider.clone();
+
+        DesktopApp::new(html! {
+            <ContextProvider<SearchProvider> context={search_context}>
+                <ContextProvider<RemoteList> {context}>
+                    {body}
+                </ContextProvider<RemoteList>>
+            </ContextProvider<SearchProvider>>
+        })
         .into()
     }
 }
diff --git a/ui/src/search_provider.rs b/ui/src/search_provider.rs
new file mode 100644
index 0000000..441cc2b
--- /dev/null
+++ b/ui/src/search_provider.rs
@@ -0,0 +1,35 @@
+use yew::Callback;
+
+use pwt::state::{SharedState, SharedStateObserver};
+
+use pdm_search::Search;
+
+#[derive(Clone, PartialEq)]
+pub struct SearchProvider {
+    state: SharedState<String>,
+}
+
+impl SearchProvider {
+    pub fn new() -> Self {
+        Self {
+            state: SharedState::new("".into()),
+        }
+    }
+
+    pub fn add_listener(
+        &self,
+        cb: impl Into<Callback<SharedState<String>>>,
+    ) -> SharedStateObserver<String> {
+        self.state.add_listener(cb)
+    }
+
+    pub fn search(&self, search_term: Search) {
+        **self.state.write() = search_term.to_string();
+    }
+}
+
+pub fn get_search_provider<T: yew::Component>(ctx: &yew::Context<T>) -> Option<SearchProvider> {
+    let (provider, _context_listener) = ctx.link().context(Callback::from(|_| {}))?;
+
+    Some(provider)
+}
diff --git a/ui/src/widget/search_box.rs b/ui/src/widget/search_box.rs
index 0aeedb7..6b2478f 100644
--- a/ui/src/widget/search_box.rs
+++ b/ui/src/widget/search_box.rs
@@ -9,13 +9,15 @@ use yew::{
 };
 
 use pwt::{
-    dom::focus::FocusTracker,
-    dom::IntoHtmlElement,
+    dom::{focus::FocusTracker, IntoHtmlElement},
     prelude::*,
     props::CssLength,
+    state::{SharedState, SharedStateObserver},
     widget::{form::Field, Container},
 };
 
+use crate::search_provider::get_search_provider;
+
 use super::ResourceTree;
 
 #[derive(Properties, PartialEq)]
@@ -35,7 +37,7 @@ impl From<SearchBox> for VNode {
 }
 
 pub enum Msg {
-    ChangeTerm(String),
+    ChangeTerm(String, bool), // force value
     FocusChange(bool),
     ToggleFocus,
 }
@@ -48,6 +50,8 @@ pub struct PdmSearchBox {
     focus: bool,
     global_shortcut_listener: Closure<dyn Fn(KeyboardEvent)>,
     toggle_focus: bool,
+    _observer: Option<SharedStateObserver<String>>,
+    force_value: bool,
 }
 
 impl Component for PdmSearchBox {
@@ -57,6 +61,14 @@ impl Component for PdmSearchBox {
 
     fn create(ctx: &yew::Context<Self>) -> Self {
         let link = ctx.link().clone();
+        let _observer = get_search_provider(ctx).map(|search| {
+            search.add_listener(ctx.link().batch_callback(|value: SharedState<String>| {
+                vec![
+                    Msg::ToggleFocus,
+                    Msg::ChangeTerm(value.read().clone(), true),
+                ]
+            }))
+        });
         Self {
             search_field_ref: Default::default(),
             search_box_ref: Default::default(),
@@ -72,13 +84,16 @@ impl Component for PdmSearchBox {
                     _ => {}
                 }
             })),
+            _observer,
+            force_value: false,
         }
     }
 
     fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
         match msg {
-            Msg::ChangeTerm(term) => {
+            Msg::ChangeTerm(term, force_value) => {
                 self.search_term = term;
+                self.force_value = force_value;
                 true
             }
             Msg::FocusChange(focus) => {
@@ -122,7 +137,8 @@ impl Component for PdmSearchBox {
                 Field::new()
                     .placeholder(tr!("Search (Ctrl+Space / Ctrl+Shift+F)"))
                     .node_ref(self.search_field_ref.clone())
-                    .on_input(ctx.link().callback(Msg::ChangeTerm)),
+                    .value(self.force_value.then_some(self.search_term.clone()))
+                    .on_input(ctx.link().callback(|term| Msg::ChangeTerm(term, false))),
             )
             .with_child(search_result)
             .into()
-- 
2.39.5





More information about the pdm-devel mailing list