pinakes-ui: add SettingsSection widget target; align location strings with schema constants

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9a5b91457136254fdf5fa582899079e46a6a6964
This commit is contained in:
raf 2026-03-11 17:02:29 +03:00
commit 0baa57d48d
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -4,10 +4,15 @@
//! predefined locations. Unlike full pages, widgets have no data sources of //! predefined locations. Unlike full pages, widgets have no data sources of
//! their own and render with empty data context. //! their own and render with empty data context.
use dioxus::prelude::*; use std::collections::HashMap;
use pinakes_plugin_api::UiWidget;
use super::{data::PluginPageData, renderer::render_element}; use dioxus::prelude::*;
use pinakes_plugin_api::{ActionDefinition, UiWidget, widget_location};
use super::{
data::PluginPageData,
renderer::{RenderContext, render_element},
};
use crate::client::ApiClient; use crate::client::ApiClient;
/// Predefined injection points in the host UI. /// Predefined injection points in the host UI.
@ -21,6 +26,7 @@ pub enum WidgetLocation {
LibrarySidebar, LibrarySidebar,
DetailPanel, DetailPanel,
SearchFilters, SearchFilters,
SettingsSection,
} }
impl WidgetLocation { impl WidgetLocation {
@ -28,10 +34,11 @@ impl WidgetLocation {
#[must_use] #[must_use]
pub const fn as_str(self) -> &'static str { pub const fn as_str(self) -> &'static str {
match self { match self {
Self::LibraryHeader => "library_header", Self::LibraryHeader => widget_location::LIBRARY_HEADER,
Self::LibrarySidebar => "library_sidebar", Self::LibrarySidebar => widget_location::LIBRARY_SIDEBAR,
Self::DetailPanel => "detail_panel", Self::DetailPanel => widget_location::DETAIL_PANEL,
Self::SearchFilters => "search_filters", Self::SearchFilters => widget_location::SEARCH_FILTERS,
Self::SettingsSection => widget_location::SETTINGS_SECTION,
} }
} }
} }
@ -41,11 +48,13 @@ impl WidgetLocation {
pub struct WidgetContainerProps { pub struct WidgetContainerProps {
/// Injection point to render widgets for. /// Injection point to render widgets for.
pub location: WidgetLocation, pub location: WidgetLocation,
/// All widgets from all plugins (plugin_id, widget) pairs.
pub widgets: Vec<(String, UiWidget)>, /// All widgets from all plugins (`plugin_id`, widget) pairs.
/// API client (unused by widgets themselves but threaded through for pub widgets: Vec<(String, UiWidget)>,
/// consistency with the rest of the plugin UI system).
pub client: Signal<ApiClient>, /// API client. It is actually unused by widgets themselves but threaded
/// through for consistency with the rest of the plugin UI system.
pub client: Signal<ApiClient>,
} }
/// Renders all widgets registered for a specific [`WidgetLocation`]. /// Renders all widgets registered for a specific [`WidgetLocation`].
@ -107,12 +116,54 @@ pub struct WidgetViewRendererProps {
#[component] #[component]
pub fn WidgetViewRenderer(props: WidgetViewRendererProps) -> Element { pub fn WidgetViewRenderer(props: WidgetViewRendererProps) -> Element {
let empty_data = PluginPageData::default(); let empty_data = PluginPageData::default();
let feedback = use_signal(|| None::<(String, bool)>);
let navigate = use_signal(|| None::<String>);
let refresh = use_signal(|| 0u32);
let modal = use_signal(|| None::<pinakes_plugin_api::UiElement>);
let local_state = use_signal(HashMap::<String, serde_json::Value>::new);
let ctx = RenderContext {
client: props.client,
feedback,
navigate,
refresh,
modal,
local_state,
};
let empty_actions: HashMap<String, ActionDefinition> = HashMap::new();
rsx! { rsx! {
div { div {
class: "plugin-widget", class: "plugin-widget",
"data-plugin-id": props.plugin_id.clone(), "data-plugin-id": props.plugin_id.clone(),
"data-widget-id": props.widget.id.clone(), "data-widget-id": props.widget.id,
{ render_element(&props.widget.content, &empty_data, props.client) } { render_element(&props.widget.content, &empty_data, &empty_actions, ctx) }
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_widget_location_settings_section_str() {
assert_eq!(WidgetLocation::SettingsSection.as_str(), "settings_section");
}
#[test]
fn test_widget_location_all_variants_unique() {
let locations = [
WidgetLocation::LibraryHeader,
WidgetLocation::LibrarySidebar,
WidgetLocation::DetailPanel,
WidgetLocation::SearchFilters,
WidgetLocation::SettingsSection,
];
let strings: Vec<&str> = locations.iter().map(|l| l.as_str()).collect();
let unique: std::collections::HashSet<_> = strings.iter().collect();
assert_eq!(
strings.len(),
unique.len(),
"all location strings must be unique"
);
}
}