diff --git a/Cargo.lock b/Cargo.lock index 9dde6a8..682fbca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5516,6 +5516,7 @@ dependencies = [ "gloo-timers", "grass", "gray_matter", + "pinakes-plugin-api", "pulldown-cmark", "rand 0.10.0", "regex", diff --git a/crates/pinakes-ui/Cargo.toml b/crates/pinakes-ui/Cargo.toml index a8dcc21..6dd8a0e 100644 --- a/crates/pinakes-ui/Cargo.toml +++ b/crates/pinakes-ui/Cargo.toml @@ -26,6 +26,7 @@ dioxus-free-icons = { workspace = true } gloo-timers = { workspace = true } rand = { workspace = true } urlencoding = { workspace = true } +pinakes-plugin-api = { workspace = true } [lints] workspace = true diff --git a/crates/pinakes-ui/src/client.rs b/crates/pinakes-ui/src/client.rs index 64d1c4c..b80bec6 100644 --- a/crates/pinakes-ui/src/client.rs +++ b/crates/pinakes-ui/src/client.rs @@ -19,12 +19,25 @@ pub struct MediaUpdateEvent { pub description: Option, } -#[derive(Clone)] pub struct ApiClient { client: Client, base_url: String, } +impl Clone for ApiClient { + fn clone(&self) -> Self { + Self::new(&self.base_url, None) + } +} + +impl std::fmt::Debug for ApiClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiClient") + .field("base_url", &self.base_url) + .finish() + } +} + impl PartialEq for ApiClient { fn eq(&self, other: &Self) -> bool { self.base_url == other.base_url @@ -1598,6 +1611,49 @@ impl ApiClient { .build() .unwrap_or_else(|_| Client::new()); } + + /// List all UI pages provided by loaded plugins. + /// + /// Returns a vector of `(plugin_id, page)` tuples. + pub async fn get_plugin_ui_pages( + &self, + ) -> Result> { + #[derive(Deserialize)] + struct PageEntry { + plugin_id: String, + page: pinakes_plugin_api::UiPage, + } + + let entries: Vec = self + .client + .get(self.url("/plugins/ui-pages")) + .send() + .await? + .error_for_status()? + .json() + .await?; + + Ok(entries.into_iter().map(|e| (e.plugin_id, e.page)).collect()) + } + + /// Make a raw HTTP request to an API path. + /// + /// The `path` is appended to the base URL without any prefix. + /// Use this for plugin action endpoints that specify full API paths. + pub fn raw_request( + &self, + method: reqwest::Method, + path: &str, + ) -> reqwest::RequestBuilder { + let url = format!("{}{}", self.base_url, path); + self.client.request(method, url) + } +} + +impl Default for ApiClient { + fn default() -> Self { + Self::new("", None) + } } #[cfg(test)]