treewide: replace std hashers with rustc_hash alternatives; fix clippy
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I766c36cb53d3d7f9e85b91a67c4131a66a6a6964
This commit is contained in:
parent
0e79ba0518
commit
c6efd3661f
53 changed files with 343 additions and 394 deletions
|
|
@ -28,6 +28,7 @@ gloo-timers = { workspace = true }
|
|||
rand = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
pinakes-plugin-api = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use reqwest::{Client, header};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Payload for import events: (path, tag_ids, new_tags, collection_id)
|
||||
|
|
@ -66,7 +65,7 @@ pub struct MediaResponse {
|
|||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub has_thumbnail: bool,
|
||||
pub custom_fields: HashMap<String, CustomFieldResponse>,
|
||||
pub custom_fields: FxHashMap<String, CustomFieldResponse>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
#[serde(default)]
|
||||
|
|
@ -395,7 +394,7 @@ pub struct BookMetadataResponse {
|
|||
pub format: Option<String>,
|
||||
pub authors: Vec<BookAuthorResponse>,
|
||||
#[serde(default)]
|
||||
pub identifiers: HashMap<String, Vec<String>>,
|
||||
pub identifiers: FxHashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
|
|
@ -1680,7 +1679,7 @@ impl ApiClient {
|
|||
/// Returns a map of CSS property names to values.
|
||||
pub async fn get_plugin_ui_theme_extensions(
|
||||
&self,
|
||||
) -> Result<HashMap<String, String>> {
|
||||
) -> Result<FxHashMap<String, String>> {
|
||||
Ok(
|
||||
self
|
||||
.client
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
//! Graph visualization component for markdown note connections.
|
||||
//!
|
||||
//! Renders a force-directed graph showing connections between notes.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::client::{
|
||||
ApiClient,
|
||||
|
|
@ -298,7 +297,7 @@ fn ForceDirectedGraph(
|
|||
|
||||
// Create id to position map
|
||||
let nodes_read = physics_nodes.read();
|
||||
let id_to_pos: HashMap<&str, (f64, f64)> = nodes_read
|
||||
let id_to_pos: FxHashMap<&str, (f64, f64)> = nodes_read
|
||||
.iter()
|
||||
.map(|n| (n.id.as_str(), (n.x, n.y)))
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::utils::{format_size, type_badge_class};
|
||||
use crate::client::{
|
||||
|
|
@ -50,7 +49,7 @@ pub fn Import(
|
|||
let mut filter_max_size = use_signal(|| 0u64); // 0 means no limit
|
||||
|
||||
// File selection state
|
||||
let mut selected_file_paths = use_signal(HashSet::<String>::new);
|
||||
let mut selected_file_paths = use_signal(FxHashSet::<String>::default);
|
||||
|
||||
let current_mode = *import_mode.read();
|
||||
|
||||
|
|
@ -475,7 +474,7 @@ pub fn Import(
|
|||
button {
|
||||
class: "btn btn-sm btn-ghost",
|
||||
onclick: move |_| {
|
||||
selected_file_paths.set(HashSet::new());
|
||||
selected_file_paths.set(FxHashSet::default());
|
||||
},
|
||||
"Deselect All"
|
||||
}
|
||||
|
|
@ -496,12 +495,12 @@ pub fn Import(
|
|||
let filtered_paths = filtered_paths.clone();
|
||||
move |_| {
|
||||
if all_filtered_selected {
|
||||
let filtered_set: HashSet<String> = filtered_paths
|
||||
let filtered_set: FxHashSet<String> = filtered_paths
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let sel = selected_file_paths.read().clone();
|
||||
let new_sel: HashSet<String> = sel
|
||||
let new_sel: FxHashSet<String> = sel
|
||||
.difference(&filtered_set)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
|
@ -599,7 +598,7 @@ pub fn Import(
|
|||
let new_tags = parse_new_tags(&new_tags_input.read());
|
||||
let col_id = selected_collection.read().clone();
|
||||
on_import_batch.call((paths, tag_ids, new_tags, col_id));
|
||||
selected_file_paths.set(HashSet::new());
|
||||
selected_file_paths.set(FxHashSet::default());
|
||||
selected_tags.set(Vec::new());
|
||||
new_tags_input.set(String::new());
|
||||
selected_collection.set(None);
|
||||
|
|
@ -644,7 +643,7 @@ pub fn Import(
|
|||
selected_tags.set(Vec::new());
|
||||
new_tags_input.set(String::new());
|
||||
selected_collection.set(None);
|
||||
selected_file_paths.set(HashSet::new());
|
||||
selected_file_paths.set(FxHashSet::default());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -316,6 +316,10 @@ fn escape_html_attr(text: &str) -> String {
|
|||
|
||||
/// Sanitize HTML using ammonia with a safe allowlist.
|
||||
/// This prevents XSS attacks by removing dangerous elements and attributes.
|
||||
#[expect(
|
||||
clippy::disallowed_types,
|
||||
reason = "ammonia::Builder requires std HashSet"
|
||||
)]
|
||||
fn sanitize_html(html: &str) -> String {
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
//! This module provides the action execution system that handles
|
||||
//! user interactions with plugin UI elements.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pinakes_plugin_api::{
|
||||
ActionDefinition,
|
||||
ActionRef,
|
||||
|
|
@ -12,6 +10,7 @@ use pinakes_plugin_api::{
|
|||
SpecialAction,
|
||||
UiElement,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::data::to_reqwest_method;
|
||||
use crate::client::ApiClient;
|
||||
|
|
@ -48,7 +47,7 @@ pub enum ActionResult {
|
|||
pub async fn execute_action(
|
||||
client: &ApiClient,
|
||||
action_ref: &ActionRef,
|
||||
page_actions: &HashMap<String, ActionDefinition>,
|
||||
page_actions: &FxHashMap<String, ActionDefinition>,
|
||||
form_data: Option<&serde_json::Value>,
|
||||
) -> Result<ActionResult, String> {
|
||||
match action_ref {
|
||||
|
|
@ -224,9 +223,10 @@ mod tests {
|
|||
async fn test_named_action_unknown_returns_none() {
|
||||
let client = crate::client::ApiClient::default();
|
||||
let action_ref = ActionRef::Name("my-action".to_string());
|
||||
let result = execute_action(&client, &action_ref, &HashMap::new(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
execute_action(&client, &action_ref, &FxHashMap::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(result, ActionResult::None));
|
||||
}
|
||||
|
||||
|
|
@ -235,11 +235,11 @@ mod tests {
|
|||
use pinakes_plugin_api::ActionDefinition;
|
||||
|
||||
let client = crate::client::ApiClient::default();
|
||||
let mut page_actions = HashMap::new();
|
||||
let mut page_actions = FxHashMap::default();
|
||||
page_actions.insert("do-thing".to_string(), ActionDefinition {
|
||||
method: pinakes_plugin_api::HttpMethod::Post,
|
||||
path: "/api/v1/nonexistent-endpoint".to_string(),
|
||||
params: HashMap::new(),
|
||||
params: FxHashMap::default(),
|
||||
success_message: None,
|
||||
error_message: None,
|
||||
navigate_to: None,
|
||||
|
|
@ -267,9 +267,10 @@ mod tests {
|
|||
|
||||
let client = crate::client::ApiClient::default();
|
||||
let action_ref = ActionRef::Special(SpecialAction::Refresh);
|
||||
let result = execute_action(&client, &action_ref, &HashMap::new(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
execute_action(&client, &action_ref, &FxHashMap::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(result, ActionResult::Refresh));
|
||||
}
|
||||
|
||||
|
|
@ -281,9 +282,10 @@ mod tests {
|
|||
let action_ref = ActionRef::Special(SpecialAction::Navigate {
|
||||
to: "/dashboard".to_string(),
|
||||
});
|
||||
let result = execute_action(&client, &action_ref, &HashMap::new(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
execute_action(&client, &action_ref, &FxHashMap::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(result, ActionResult::Navigate(ref p) if p == "/dashboard")
|
||||
);
|
||||
|
|
@ -299,9 +301,10 @@ mod tests {
|
|||
key: "count".to_string(),
|
||||
value: expr.clone(),
|
||||
});
|
||||
let result = execute_action(&client, &action_ref, &HashMap::new(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
execute_action(&client, &action_ref, &FxHashMap::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
match result {
|
||||
ActionResult::UpdateState { key, value_expr } => {
|
||||
assert_eq!(key, "count");
|
||||
|
|
@ -317,9 +320,10 @@ mod tests {
|
|||
|
||||
let client = crate::client::ApiClient::default();
|
||||
let action_ref = ActionRef::Special(SpecialAction::CloseModal);
|
||||
let result = execute_action(&client, &action_ref, &HashMap::new(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
execute_action(&client, &action_ref, &FxHashMap::default(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(result, ActionResult::CloseModal));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@
|
|||
//!
|
||||
//! Provides data fetching and caching for plugin data sources.
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
time::Duration,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::Task;
|
||||
use pinakes_plugin_api::{DataSource, Expression, HttpMethod};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use super::expr::{evaluate_expression, value_to_display_string};
|
||||
use crate::client::ApiClient;
|
||||
|
|
@ -17,9 +15,9 @@ use crate::client::ApiClient;
|
|||
/// Cached data for a plugin page
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct PluginPageData {
|
||||
data: HashMap<String, serde_json::Value>,
|
||||
loading: HashSet<String>,
|
||||
errors: HashMap<String, String>,
|
||||
data: FxHashMap<String, serde_json::Value>,
|
||||
loading: FxHashSet<String>,
|
||||
errors: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
impl PluginPageData {
|
||||
|
|
@ -105,7 +103,7 @@ async fn fetch_endpoint(
|
|||
client: &ApiClient,
|
||||
path: &str,
|
||||
method: HttpMethod,
|
||||
params: &HashMap<String, Expression>,
|
||||
params: &FxHashMap<String, Expression>,
|
||||
ctx: &serde_json::Value,
|
||||
allowed_endpoints: &[String],
|
||||
) -> Result<serde_json::Value, String> {
|
||||
|
|
@ -174,9 +172,9 @@ async fn fetch_endpoint(
|
|||
/// Returns an error if any data source fails to fetch
|
||||
pub async fn fetch_page_data(
|
||||
client: &ApiClient,
|
||||
data_sources: &HashMap<String, DataSource>,
|
||||
data_sources: &FxHashMap<String, DataSource>,
|
||||
allowed_endpoints: &[String],
|
||||
) -> Result<HashMap<String, serde_json::Value>, String> {
|
||||
) -> Result<FxHashMap<String, serde_json::Value>, String> {
|
||||
// Group non-Transform sources into dedup groups.
|
||||
//
|
||||
// For Endpoint sources, two entries are in the same group when they share
|
||||
|
|
@ -300,7 +298,7 @@ pub async fn fetch_page_data(
|
|||
})
|
||||
.collect();
|
||||
|
||||
let mut results: HashMap<String, serde_json::Value> = HashMap::new();
|
||||
let mut results: FxHashMap<String, serde_json::Value> = FxHashMap::default();
|
||||
for group_result in futures::future::join_all(futs).await {
|
||||
for (name, value) in group_result? {
|
||||
results.insert(name, value);
|
||||
|
|
@ -375,7 +373,7 @@ pub async fn fetch_page_data(
|
|||
/// immediate re-fetch outside of the polling interval.
|
||||
pub fn use_plugin_data(
|
||||
client: Signal<ApiClient>,
|
||||
data_sources: HashMap<String, DataSource>,
|
||||
data_sources: FxHashMap<String, DataSource>,
|
||||
refresh: Signal<u32>,
|
||||
allowed_endpoints: Vec<String>,
|
||||
) -> Signal<PluginPageData> {
|
||||
|
|
@ -564,7 +562,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
sources.insert("nums".to_string(), DataSource::Static {
|
||||
value: serde_json::json!([1, 2, 3]),
|
||||
});
|
||||
|
|
@ -586,7 +584,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
// The Transform expression accesses "raw" from the context
|
||||
sources.insert("derived".to_string(), DataSource::Transform {
|
||||
source_name: "raw".to_string(),
|
||||
|
|
@ -611,7 +609,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
sources.insert("raw".to_string(), DataSource::Static {
|
||||
value: serde_json::json!(42),
|
||||
});
|
||||
|
|
@ -634,7 +632,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
// Two Static sources with the same payload; dedup is for Endpoint sources,
|
||||
// but both names must appear in the output regardless.
|
||||
sources.insert("a".to_string(), DataSource::Static {
|
||||
|
|
@ -662,7 +660,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
// Two endpoints with identical (path, method, params=empty) but different
|
||||
// transforms. Both should produce the same error when the path is blocked.
|
||||
sources.insert("x".to_string(), DataSource::Endpoint {
|
||||
|
|
@ -707,7 +705,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
sources.insert("raw_data".to_string(), DataSource::Static {
|
||||
value: serde_json::json!({"count": 42, "name": "test"}),
|
||||
});
|
||||
|
|
@ -741,7 +739,7 @@ mod tests {
|
|||
use crate::client::ApiClient;
|
||||
|
||||
let client = ApiClient::default();
|
||||
let mut sources = HashMap::new();
|
||||
let mut sources = FxHashMap::default();
|
||||
sources.insert("items".to_string(), DataSource::Endpoint {
|
||||
path: "/api/v1/media".to_string(),
|
||||
method: HttpMethod::Get,
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@
|
|||
//! }
|
||||
//! ```
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use pinakes_plugin_api::{UiPage, UiWidget};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::client::ApiClient;
|
||||
|
||||
|
|
@ -43,11 +42,11 @@ pub struct PluginRegistry {
|
|||
/// API client for fetching pages from server
|
||||
client: ApiClient,
|
||||
/// Cached pages: (`plugin_id`, `page_id`) -> `PluginPage`
|
||||
pages: HashMap<(String, String), PluginPage>,
|
||||
pages: FxHashMap<(String, String), PluginPage>,
|
||||
/// Cached widgets: (`plugin_id`, `widget_id`) -> `UiWidget`
|
||||
widgets: Vec<(String, UiWidget)>,
|
||||
/// Merged CSS custom property overrides from all enabled plugins
|
||||
theme_vars: HashMap<String, String>,
|
||||
theme_vars: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
impl PluginRegistry {
|
||||
|
|
@ -55,14 +54,14 @@ impl PluginRegistry {
|
|||
pub fn new(client: ApiClient) -> Self {
|
||||
Self {
|
||||
client,
|
||||
pages: HashMap::new(),
|
||||
pages: FxHashMap::default(),
|
||||
widgets: Vec::new(),
|
||||
theme_vars: HashMap::new(),
|
||||
theme_vars: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get merged CSS custom property overrides from all loaded plugins.
|
||||
pub fn theme_vars(&self) -> &HashMap<String, String> {
|
||||
pub fn theme_vars(&self) -> &FxHashMap<String, String> {
|
||||
&self.theme_vars
|
||||
}
|
||||
|
||||
|
|
@ -230,8 +229,8 @@ mod tests {
|
|||
gap: 16,
|
||||
padding: None,
|
||||
},
|
||||
data_sources: HashMap::new(),
|
||||
actions: HashMap::new(),
|
||||
data_sources: FxHashMap::default(),
|
||||
actions: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -491,8 +490,8 @@ mod tests {
|
|||
gap: 16,
|
||||
padding: None,
|
||||
},
|
||||
data_sources: HashMap::new(),
|
||||
actions: HashMap::new(),
|
||||
data_sources: FxHashMap::default(),
|
||||
actions: FxHashMap::default(),
|
||||
};
|
||||
|
||||
registry.register_page("test-plugin".to_string(), invalid_page, vec![]);
|
||||
|
|
@ -517,8 +516,8 @@ mod tests {
|
|||
gap: 0,
|
||||
padding: None,
|
||||
},
|
||||
data_sources: HashMap::new(),
|
||||
actions: HashMap::new(),
|
||||
data_sources: FxHashMap::default(),
|
||||
actions: FxHashMap::default(),
|
||||
};
|
||||
registry.register_page("p".to_string(), invalid_page, vec![]);
|
||||
assert_eq!(registry.all_pages().len(), 0);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
//! elements. Data-driven elements resolve their data from a [`PluginPageData`]
|
||||
//! context that is populated by the `use_plugin_data` hook.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use pinakes_plugin_api::{
|
||||
ActionDefinition,
|
||||
|
|
@ -23,6 +21,7 @@ use pinakes_plugin_api::{
|
|||
UiElement,
|
||||
UiPage,
|
||||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use super::{
|
||||
actions::execute_action,
|
||||
|
|
@ -49,13 +48,13 @@ pub struct RenderContext {
|
|||
pub navigate: Signal<Option<String>>,
|
||||
pub refresh: Signal<u32>,
|
||||
pub modal: Signal<Option<UiElement>>,
|
||||
pub local_state: Signal<HashMap<String, serde_json::Value>>,
|
||||
pub local_state: Signal<FxHashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
/// Build the expression evaluation context from page data and local state.
|
||||
fn build_ctx(
|
||||
data: &PluginPageData,
|
||||
local_state: &HashMap<String, serde_json::Value>,
|
||||
local_state: &FxHashMap<String, serde_json::Value>,
|
||||
) -> serde_json::Value {
|
||||
let mut base = data.as_json();
|
||||
if let serde_json::Value::Object(ref mut obj) = base {
|
||||
|
|
@ -101,7 +100,7 @@ pub fn PluginViewRenderer(props: PluginViewProps) -> Element {
|
|||
let mut navigate = use_signal(|| None::<String>);
|
||||
let refresh = use_signal(|| 0u32);
|
||||
let mut modal = use_signal(|| None::<UiElement>);
|
||||
let local_state = use_signal(HashMap::<String, serde_json::Value>::new);
|
||||
let local_state = use_signal(FxHashMap::<String, serde_json::Value>::default);
|
||||
let ctx = RenderContext {
|
||||
client: props.client,
|
||||
feedback,
|
||||
|
|
@ -169,7 +168,7 @@ struct PluginTabsProps {
|
|||
tabs: Vec<TabDefinition>,
|
||||
default_tab: usize,
|
||||
data: PluginPageData,
|
||||
actions: HashMap<String, ActionDefinition>,
|
||||
actions: FxHashMap<String, ActionDefinition>,
|
||||
ctx: RenderContext,
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +231,7 @@ struct PluginDataTableProps {
|
|||
page_size: usize,
|
||||
row_actions: Vec<pinakes_plugin_api::RowAction>,
|
||||
data: PluginPageData,
|
||||
actions: HashMap<String, ActionDefinition>,
|
||||
actions: FxHashMap<String, ActionDefinition>,
|
||||
ctx: RenderContext,
|
||||
}
|
||||
|
||||
|
|
@ -472,7 +471,7 @@ fn PluginDataTable(props: PluginDataTableProps) -> Element {
|
|||
pub fn render_element(
|
||||
element: &UiElement,
|
||||
data: &PluginPageData,
|
||||
actions: &HashMap<String, ActionDefinition>,
|
||||
actions: &FxHashMap<String, ActionDefinition>,
|
||||
ctx: RenderContext,
|
||||
) -> Element {
|
||||
match element {
|
||||
|
|
@ -1188,7 +1187,7 @@ fn render_chart_data(
|
|||
Some(serde_json::Value::Array(arr)) if !arr.is_empty() => {
|
||||
if arr.first().map(|v| v.is_object()).unwrap_or(false) {
|
||||
// Object rows: collect unique keys preserving insertion order
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut seen = FxHashSet::default();
|
||||
let cols: Vec<String> = arr
|
||||
.iter()
|
||||
.filter_map(|r| r.as_object())
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
//! predefined locations. Unlike full pages, widgets have no data sources of
|
||||
//! their own and render with empty data context.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use pinakes_plugin_api::{ActionDefinition, UiWidget, widget_location};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::{
|
||||
data::PluginPageData,
|
||||
|
|
@ -120,7 +119,7 @@ pub fn WidgetViewRenderer(props: WidgetViewRendererProps) -> Element {
|
|||
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 local_state = use_signal(FxHashMap::<String, serde_json::Value>::default);
|
||||
let ctx = RenderContext {
|
||||
client: props.client,
|
||||
feedback,
|
||||
|
|
@ -129,7 +128,7 @@ pub fn WidgetViewRenderer(props: WidgetViewRendererProps) -> Element {
|
|||
modal,
|
||||
local_state,
|
||||
};
|
||||
let empty_actions: HashMap<String, ActionDefinition> = HashMap::new();
|
||||
let empty_actions: FxHashMap<String, ActionDefinition> = FxHashMap::default();
|
||||
rsx! {
|
||||
div {
|
||||
class: "plugin-widget",
|
||||
|
|
@ -142,6 +141,8 @@ pub fn WidgetViewRenderer(props: WidgetViewRendererProps) -> Element {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
|
@ -159,7 +160,7 @@ mod tests {
|
|||
WidgetLocation::SettingsSection,
|
||||
];
|
||||
let strings: Vec<&str> = locations.iter().map(|l| l.as_str()).collect();
|
||||
let unique: std::collections::HashSet<_> = strings.iter().collect();
|
||||
let unique: FxHashSet<_> = strings.iter().collect();
|
||||
assert_eq!(
|
||||
strings.len(),
|
||||
unique.len(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue