GUI plugins #9
2 changed files with 196 additions and 40 deletions
pinakes-ui: integrate plugin registry into app navigation and routing
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I7c4593d93693bf08555a0b5f89a67aea6a6a6964
commit
6e442065b1
|
|
@ -59,6 +59,7 @@ use crate::{
|
||||||
tags,
|
tags,
|
||||||
tasks,
|
tasks,
|
||||||
},
|
},
|
||||||
|
plugin_ui::{PluginRegistry, PluginViewRenderer},
|
||||||
styles,
|
styles,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -80,6 +81,10 @@ enum View {
|
||||||
Settings,
|
Settings,
|
||||||
Database,
|
Database,
|
||||||
Graph,
|
Graph,
|
||||||
|
PluginView {
|
||||||
|
plugin_id: String,
|
||||||
|
page_id: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
|
@ -99,13 +104,13 @@ impl View {
|
||||||
Self::Settings => "Settings",
|
Self::Settings => "Settings",
|
||||||
Self::Database => "Database",
|
Self::Database => "Database",
|
||||||
Self::Graph => "Note Graph",
|
Self::Graph => "Note Graph",
|
||||||
|
Self::PluginView { .. } => "Plugin",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App() -> Element {
|
pub fn App() -> Element {
|
||||||
// Phase 1.3: Auth support
|
|
||||||
let base_url = std::env::var("PINAKES_SERVER_URL")
|
let base_url = std::env::var("PINAKES_SERVER_URL")
|
||||||
.unwrap_or_else(|_| "http://localhost:3000".into());
|
.unwrap_or_else(|_| "http://localhost:3000".into());
|
||||||
let api_key = std::env::var("PINAKES_API_KEY").ok();
|
let api_key = std::env::var("PINAKES_API_KEY").ok();
|
||||||
|
|
@ -139,7 +144,6 @@ pub fn App() -> Element {
|
||||||
let mut viewing_collection = use_signal(|| Option::<String>::None);
|
let mut viewing_collection = use_signal(|| Option::<String>::None);
|
||||||
let mut collection_members = use_signal(Vec::<MediaResponse>::new);
|
let mut collection_members = use_signal(Vec::<MediaResponse>::new);
|
||||||
|
|
||||||
// Phase 4A: Book management
|
|
||||||
let mut books_list = use_signal(Vec::<MediaResponse>::new);
|
let mut books_list = use_signal(Vec::<MediaResponse>::new);
|
||||||
let mut books_series_list =
|
let mut books_series_list =
|
||||||
use_signal(Vec::<crate::client::SeriesSummary>::new);
|
use_signal(Vec::<crate::client::SeriesSummary>::new);
|
||||||
|
|
@ -160,31 +164,24 @@ pub fn App() -> Element {
|
||||||
let mut loading = use_signal(|| true);
|
let mut loading = use_signal(|| true);
|
||||||
let mut load_error = use_signal(|| Option::<String>::None);
|
let mut load_error = use_signal(|| Option::<String>::None);
|
||||||
|
|
||||||
// Phase 1.4: Toast queue
|
|
||||||
let mut toast_queue = use_signal(Vec::<(String, bool, usize)>::new);
|
let mut toast_queue = use_signal(Vec::<(String, bool, usize)>::new);
|
||||||
|
|
||||||
// Phase 5.1: Search pagination
|
|
||||||
let mut search_page = use_signal(|| 0u64);
|
let mut search_page = use_signal(|| 0u64);
|
||||||
let search_page_size = use_signal(|| 50u64);
|
let search_page_size = use_signal(|| 50u64);
|
||||||
let mut last_search_query = use_signal(String::new);
|
let mut last_search_query = use_signal(String::new);
|
||||||
let mut last_search_sort = use_signal(|| Option::<String>::None);
|
let mut last_search_sort = use_signal(|| Option::<String>::None);
|
||||||
|
|
||||||
// Phase 3.6: Saved searches
|
|
||||||
let mut saved_searches = use_signal(Vec::<SavedSearchResponse>::new);
|
let mut saved_searches = use_signal(Vec::<SavedSearchResponse>::new);
|
||||||
|
|
||||||
// Phase 6.1: Audit pagination & filter
|
|
||||||
let mut audit_page = use_signal(|| 0u64);
|
let mut audit_page = use_signal(|| 0u64);
|
||||||
let audit_page_size = use_signal(|| 200u64);
|
let audit_page_size = use_signal(|| 200u64);
|
||||||
let audit_total_count = use_signal(|| 0u64);
|
let audit_total_count = use_signal(|| 0u64);
|
||||||
let mut audit_filter = use_signal(|| "All".to_string());
|
let mut audit_filter = use_signal(|| "All".to_string());
|
||||||
|
|
||||||
// Phase 6.2: Scan progress
|
|
||||||
let mut scan_progress = use_signal(|| Option::<ScanStatusResponse>::None);
|
let mut scan_progress = use_signal(|| Option::<ScanStatusResponse>::None);
|
||||||
|
|
||||||
// Phase 7.1: Help overlay
|
|
||||||
let mut show_help = use_signal(|| false);
|
let mut show_help = use_signal(|| false);
|
||||||
|
|
||||||
// Phase 8: Sidebar collapse
|
|
||||||
let mut sidebar_collapsed = use_signal(|| false);
|
let mut sidebar_collapsed = use_signal(|| false);
|
||||||
|
|
||||||
// Auth state
|
// Auth state
|
||||||
|
|
@ -195,7 +192,8 @@ pub fn App() -> Element {
|
||||||
let mut auto_play_media = use_signal(|| false);
|
let mut auto_play_media = use_signal(|| false);
|
||||||
let mut play_queue = use_signal(PlayQueue::default);
|
let mut play_queue = use_signal(PlayQueue::default);
|
||||||
|
|
||||||
// Theme state (Phase 3.3)
|
let mut plugin_registry = use_signal(PluginRegistry::default);
|
||||||
|
|
||||||
let mut current_theme = use_signal(|| "dark".to_string());
|
let mut current_theme = use_signal(|| "dark".to_string());
|
||||||
let mut system_prefers_dark = use_signal(|| true);
|
let mut system_prefers_dark = use_signal(|| true);
|
||||||
|
|
||||||
|
|
@ -287,7 +285,7 @@ pub fn App() -> Element {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load initial data (Phase 2.2: pass sort to list_media)
|
// Load initial data
|
||||||
let client_init = client.read().clone();
|
let client_init = client.read().clone();
|
||||||
let init_sort = media_sort.read().clone();
|
let init_sort = media_sort.read().clone();
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
|
|
@ -311,7 +309,6 @@ pub fn App() -> Element {
|
||||||
if let Ok(c) = client.list_collections().await {
|
if let Ok(c) = client.list_collections().await {
|
||||||
collections_list.set(c);
|
collections_list.set(c);
|
||||||
}
|
}
|
||||||
// Phase 3.6: Load saved searches
|
|
||||||
if let Ok(ss) = client.list_saved_searches().await {
|
if let Ok(ss) = client.list_saved_searches().await {
|
||||||
saved_searches.set(ss);
|
saved_searches.set(ss);
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +316,22 @@ pub fn App() -> Element {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Phase 1.4: Toast helper with queue support
|
use_effect(move || {
|
||||||
|
let c = client.read().clone();
|
||||||
|
spawn(async move {
|
||||||
|
match c.get_plugin_ui_pages().await {
|
||||||
|
Ok(pages) => {
|
||||||
|
let mut reg = PluginRegistry::default();
|
||||||
|
for (plugin_id, page) in pages {
|
||||||
|
reg.register_page(plugin_id, page);
|
||||||
|
}
|
||||||
|
plugin_registry.set(reg);
|
||||||
|
},
|
||||||
|
Err(e) => tracing::debug!("Plugin pages unavailable: {e}"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let mut show_toast = move |msg: String, is_error: bool| {
|
let mut show_toast = move |msg: String, is_error: bool| {
|
||||||
let id = TOAST_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
let id = TOAST_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
toast_queue.write().push((msg, is_error, id));
|
toast_queue.write().push((msg, is_error, id));
|
||||||
|
|
@ -334,7 +346,6 @@ pub fn App() -> Element {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper: refresh media list with current pagination (Phase 2.2: pass sort)
|
|
||||||
let refresh_media = {
|
let refresh_media = {
|
||||||
let client = client.read().clone();
|
let client = client.read().clone();
|
||||||
move || {
|
move || {
|
||||||
|
|
@ -355,7 +366,6 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper: refresh tags
|
|
||||||
let refresh_tags = {
|
let refresh_tags = {
|
||||||
let client = client.read().clone();
|
let client = client.read().clone();
|
||||||
move || {
|
move || {
|
||||||
|
|
@ -368,7 +378,6 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper: refresh collections
|
|
||||||
let refresh_collections = {
|
let refresh_collections = {
|
||||||
let client = client.read().clone();
|
let client = client.read().clone();
|
||||||
move || {
|
move || {
|
||||||
|
|
@ -381,7 +390,6 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper: refresh audit with pagination and filter (Phase 6.1)
|
|
||||||
let refresh_audit = {
|
let refresh_audit = {
|
||||||
let client = client.read().clone();
|
let client = client.read().clone();
|
||||||
move || {
|
move || {
|
||||||
|
|
@ -440,7 +448,6 @@ pub fn App() -> Element {
|
||||||
loading: *login_loading.read(),
|
loading: *login_loading.read(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Phase 7.1: Keyboard shortcuts
|
|
||||||
div {
|
div {
|
||||||
class: if *effective_theme.read() == "light" { "app theme-light" } else { "app" },
|
class: if *effective_theme.read() == "light" { "app theme-light" } else { "app" },
|
||||||
tabindex: "0",
|
tabindex: "0",
|
||||||
|
|
@ -744,6 +751,36 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !plugin_registry.read().is_empty() {
|
||||||
|
div { class: "nav-section",
|
||||||
|
div { class: "nav-label", "Plugins" }
|
||||||
|
for page in plugin_registry.read().all_pages() {
|
||||||
|
{
|
||||||
|
let pid = page.plugin_id.clone();
|
||||||
|
let pageid = page.page.id.clone();
|
||||||
|
let title = page.page.title.clone();
|
||||||
|
let is_active = *current_view.read()
|
||||||
|
== View::PluginView {
|
||||||
|
plugin_id: pid.clone(),
|
||||||
|
page_id: pageid.clone(),
|
||||||
|
};
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
class: if is_active { "nav-item active" } else { "nav-item" },
|
||||||
|
onclick: move |_| {
|
||||||
|
current_view.set(View::PluginView {
|
||||||
|
plugin_id: pid.clone(),
|
||||||
|
page_id: pageid.clone(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
span { class: "nav-item-text", "{title}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div { class: "sidebar-spacer" }
|
div { class: "sidebar-spacer" }
|
||||||
|
|
||||||
// Show import progress in sidebar when not on import page
|
// Show import progress in sidebar when not on import page
|
||||||
|
|
@ -881,17 +918,6 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Phase 2.2: Sort wiring - actually refetch with sort
|
|
||||||
// Phase 4.1 + 4.2: Search improvements
|
|
||||||
// Phase 3.1 + 3.2: Detail view enhancements
|
|
||||||
// Phase 3.2: Delete from detail navigates back and refreshes
|
|
||||||
// Phase 5.1: Tags on_delete - confirmation handled inside Tags component
|
|
||||||
// Phase 5.2: Collections enhancements
|
|
||||||
// Phase 5.2: Navigate to detail when clicking a collection member
|
|
||||||
// Phase 5.2: Add member to collection
|
|
||||||
// Phase 6.1: Audit improvements
|
|
||||||
// Phase 6.2: Scan progress
|
|
||||||
// Phase 6.2: Scan with polling for progress
|
|
||||||
// Poll scan status until done
|
// Poll scan status until done
|
||||||
// Refresh duplicates list
|
// Refresh duplicates list
|
||||||
// Reload full config
|
// Reload full config
|
||||||
|
|
@ -1178,7 +1204,6 @@ pub fn App() -> Element {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Phase 3.6: Saved searches
|
|
||||||
saved_searches: saved_searches.read().clone(),
|
saved_searches: saved_searches.read().clone(),
|
||||||
on_save_search: {
|
on_save_search: {
|
||||||
let client = client.read().clone();
|
let client = client.read().clone();
|
||||||
|
|
@ -2673,12 +2698,34 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
View::PluginView {
|
||||||
|
ref plugin_id,
|
||||||
|
ref page_id,
|
||||||
|
} => {
|
||||||
|
let pid = plugin_id.clone();
|
||||||
|
let pageid = page_id.clone();
|
||||||
|
let page_opt =
|
||||||
|
plugin_registry.read().get_page(&pid, &pageid).cloned();
|
||||||
|
match page_opt {
|
||||||
|
Some(plugin_page) => rsx! {
|
||||||
|
PluginViewRenderer {
|
||||||
|
plugin_id: pid,
|
||||||
|
page: plugin_page.page,
|
||||||
|
client,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => rsx! {
|
||||||
|
div { class: "plugin-not-found",
|
||||||
|
"Plugin page not found: {pageid}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 7.1: Help overlay
|
|
||||||
if *show_help.read() {
|
if *show_help.read() {
|
||||||
div {
|
div {
|
||||||
class: "help-overlay",
|
class: "help-overlay",
|
||||||
|
|
@ -2747,7 +2794,6 @@ pub fn App() -> Element {
|
||||||
}
|
}
|
||||||
} // end else (auth not required)
|
} // end else (auth not required)
|
||||||
|
|
||||||
// Phase 1.4: Toast queue - show up to 3 stacked from bottom
|
|
||||||
div { class: "toast-container",
|
div { class: "toast-container",
|
||||||
{
|
{
|
||||||
let toasts = toast_queue.read().clone();
|
let toasts = toast_queue.read().clone();
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use pinakes_plugin_api::UiPage;
|
use pinakes_plugin_api::{UiPage, UiWidget};
|
||||||
|
|
||||||
use crate::client::ApiClient;
|
use crate::client::ApiClient;
|
||||||
|
|
||||||
|
|
@ -39,15 +39,17 @@ impl PluginPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registry of all plugin-provided UI pages
|
/// Registry of all plugin-provided UI pages and widgets
|
||||||
///
|
///
|
||||||
/// This is typically stored as a context value in the Dioxus tree.
|
/// This is typically stored as a signal in the Dioxus tree.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PluginRegistry {
|
pub struct PluginRegistry {
|
||||||
/// API client for fetching pages from server
|
/// API client for fetching pages from server
|
||||||
client: ApiClient,
|
client: ApiClient,
|
||||||
/// Cached pages: (plugin_id, page_id) -> PluginPage
|
/// Cached pages: (plugin_id, page_id) -> PluginPage
|
||||||
pages: HashMap<(String, String), PluginPage>,
|
pages: HashMap<(String, String), PluginPage>,
|
||||||
|
/// Cached widgets: (plugin_id, widget_id) -> UiWidget
|
||||||
|
widgets: Vec<(String, UiWidget)>,
|
||||||
/// Last refresh timestamp
|
/// Last refresh timestamp
|
||||||
last_refresh: Option<chrono::DateTime<chrono::Utc>>,
|
last_refresh: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
@ -58,17 +60,15 @@ impl PluginRegistry {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
pages: HashMap::new(),
|
pages: HashMap::new(),
|
||||||
|
widgets: Vec::new(),
|
||||||
last_refresh: None,
|
last_refresh: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new registry with pre-loaded pages
|
/// Create a new registry with pre-loaded pages
|
||||||
pub fn with_pages(
|
pub fn with_pages(client: ApiClient, pages: Vec<(String, UiPage)>) -> Self {
|
||||||
client: ApiClient,
|
|
||||||
pages: Vec<(String, String, UiPage)>,
|
|
||||||
) -> Self {
|
|
||||||
let mut registry = Self::new(client);
|
let mut registry = Self::new(client);
|
||||||
for (plugin_id, _page_id, page) in pages {
|
for (plugin_id, page) in pages {
|
||||||
registry.register_page(plugin_id, page);
|
registry.register_page(plugin_id, page);
|
||||||
}
|
}
|
||||||
registry
|
registry
|
||||||
|
|
@ -93,6 +93,16 @@ impl PluginRegistry {
|
||||||
.get(&(plugin_id.to_string(), page_id.to_string()))
|
.get(&(plugin_id.to_string(), page_id.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a widget from a plugin
|
||||||
|
pub fn register_widget(&mut self, plugin_id: String, widget: UiWidget) {
|
||||||
|
self.widgets.push((plugin_id, widget));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all widgets (for use with WidgetContainer)
|
||||||
|
pub fn all_widgets(&self) -> Vec<(String, UiWidget)> {
|
||||||
|
self.widgets.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all pages
|
/// Get all pages
|
||||||
pub fn all_pages(&self) -> Vec<&PluginPage> {
|
pub fn all_pages(&self) -> Vec<&PluginPage> {
|
||||||
self.pages.values().collect()
|
self.pages.values().collect()
|
||||||
|
|
@ -122,6 +132,7 @@ impl PluginRegistry {
|
||||||
match self.client.get_plugin_ui_pages().await {
|
match self.client.get_plugin_ui_pages().await {
|
||||||
Ok(pages) => {
|
Ok(pages) => {
|
||||||
self.pages.clear();
|
self.pages.clear();
|
||||||
|
self.widgets.clear();
|
||||||
for (plugin_id, page) in pages {
|
for (plugin_id, page) in pages {
|
||||||
self.register_page(plugin_id, page);
|
self.register_page(plugin_id, page);
|
||||||
}
|
}
|
||||||
|
|
@ -245,4 +256,103 @@ mod tests {
|
||||||
assert_eq!(routes[0].1, "page1");
|
assert_eq!(routes[0].1, "page1");
|
||||||
assert_eq!(routes[0].2, "/plugins/plugin1/page1");
|
assert_eq!(routes[0].2, "/plugins/plugin1/page1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_register_widget_and_all_widgets() {
|
||||||
|
let client = ApiClient::default();
|
||||||
|
let mut registry = PluginRegistry::new(client);
|
||||||
|
|
||||||
|
let widget: UiWidget = serde_json::from_value(serde_json::json!({
|
||||||
|
"id": "my-widget",
|
||||||
|
"target": "library_header",
|
||||||
|
"content": { "type": "badge", "text": "hello", "variant": "default" }
|
||||||
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(registry.all_widgets().is_empty());
|
||||||
|
registry.register_widget("test-plugin".to_string(), widget.clone());
|
||||||
|
let widgets = registry.all_widgets();
|
||||||
|
assert_eq!(widgets.len(), 1);
|
||||||
|
assert_eq!(widgets[0].0, "test-plugin");
|
||||||
|
assert_eq!(widgets[0].1.id, "my-widget");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_pages_builds_registry() {
|
||||||
|
let client = ApiClient::default();
|
||||||
|
let pages = vec![
|
||||||
|
("plugin1".to_string(), create_test_page("page1", "Page 1")),
|
||||||
|
("plugin2".to_string(), create_test_page("page2", "Page 2")),
|
||||||
|
];
|
||||||
|
|
||||||
|
let registry = PluginRegistry::with_pages(client, pages);
|
||||||
|
assert_eq!(registry.len(), 2);
|
||||||
|
assert!(registry.get_page("plugin1", "page1").is_some());
|
||||||
|
assert!(registry.get_page("plugin2", "page2").is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_register_page_overwrites_same_key() {
|
||||||
|
let client = ApiClient::default();
|
||||||
|
let mut registry = PluginRegistry::new(client);
|
||||||
|
|
||||||
|
registry
|
||||||
|
.register_page("plugin1".to_string(), create_test_page("p", "Original"));
|
||||||
|
registry
|
||||||
|
.register_page("plugin1".to_string(), create_test_page("p", "Updated"));
|
||||||
|
|
||||||
|
assert_eq!(registry.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
registry.get_page("plugin1", "p").unwrap().page.title,
|
||||||
|
"Updated"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_registry_is_empty() {
|
||||||
|
let registry = PluginRegistry::default();
|
||||||
|
assert!(registry.is_empty());
|
||||||
|
assert_eq!(registry.len(), 0);
|
||||||
|
assert!(registry.last_refresh().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_pages_returns_references() {
|
||||||
|
let client = ApiClient::default();
|
||||||
|
let mut registry = PluginRegistry::new(client);
|
||||||
|
registry.register_page("p1".to_string(), create_test_page("a", "A"));
|
||||||
|
registry.register_page("p2".to_string(), create_test_page("b", "B"));
|
||||||
|
|
||||||
|
let pages = registry.all_pages();
|
||||||
|
assert_eq!(pages.len(), 2);
|
||||||
|
let titles: Vec<&str> =
|
||||||
|
pages.iter().map(|p| p.page.title.as_str()).collect();
|
||||||
|
assert!(titles.contains(&"A"));
|
||||||
|
assert!(titles.contains(&"B"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_different_plugins_same_page_id_both_stored() {
|
||||||
|
let client = ApiClient::default();
|
||||||
|
let mut registry = PluginRegistry::new(client);
|
||||||
|
|
||||||
|
registry.register_page(
|
||||||
|
"plugin-a".to_string(),
|
||||||
|
create_test_page("home", "A Home"),
|
||||||
|
);
|
||||||
|
registry.register_page(
|
||||||
|
"plugin-b".to_string(),
|
||||||
|
create_test_page("home", "B Home"),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(registry.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
registry.get_page("plugin-a", "home").unwrap().page.title,
|
||||||
|
"A Home"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
registry.get_page("plugin-b", "home").unwrap().page.title,
|
||||||
|
"B Home"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue