perspective_viewer/components/
plugin_tab.rs1use itertools::Itertools;
19use perspective_client::config::ViewConfig;
20use yew::prelude::*;
21
22use crate::components::column_settings_sidebar::style_tab::primitive_field::{
23 BoolField, ColorField, EnumField, NumberFieldPrimitive,
24};
25use crate::config::ControlSpec;
26use crate::presentation::Presentation;
27use crate::queries::get_plugin_config_schema;
28use crate::renderer::Renderer;
29use crate::session::Session;
30use crate::tasks::send_plugin_config;
31use crate::ui::ControlGroup;
32use crate::utils::PtrEqRc;
33
34#[derive(Clone, PartialEq, Properties)]
35pub struct PluginTabProps {
36 pub view_config: PtrEqRc<ViewConfig>,
39
40 pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
50
51 pub presentation: Presentation,
53 pub renderer: Renderer,
54 pub session: Session,
55}
56
57#[function_component]
58pub fn PluginTab(props: &PluginTabProps) -> Html {
59 let schema = {
73 let renderer = props.renderer.clone();
74 let view_config = props.view_config.clone();
75 use_memo(
76 (props.plugin_config.clone(), props.view_config.clone()),
77 move |_| match get_plugin_config_schema(&renderer, &view_config) {
78 Ok(schema) => schema.fields,
79 Err(error) => {
80 tracing::error!("{}", error);
81 vec![]
82 },
83 },
84 )
85 };
86
87 let on_change = {
88 let session = props.session.clone();
89 let renderer = props.renderer.clone();
90 yew::Callback::from(move |update: crate::config::ColumnConfigFieldUpdate| {
91 send_plugin_config(&session, &renderer, update);
98 })
99 };
100
101 let on_group_toggle = {
102 let presentation = props.presentation.clone();
103 yew::Callback::from(move |(key, open): (String, bool)| {
104 presentation.set_control_group_collapsed(&key, !open);
105 })
106 };
107
108 let raw_config = &*props.plugin_config;
109 let components = render_specs(
110 &schema,
111 raw_config,
112 &on_change,
113 &props.presentation,
114 &on_group_toggle,
115 );
116 html! {
117 <div id="plugin-tab" class="sidebar_column scrollable">
118 <div id="plugin-config-container" class="tab-section">{ components }</div>
119 </div>
120 }
121}
122
123fn render_specs(
124 specs: &[ControlSpec],
125 raw_config: &serde_json::Map<String, serde_json::Value>,
126 on_change: &Callback<crate::config::ColumnConfigFieldUpdate>,
127 presentation: &Presentation,
128 on_group_toggle: &Callback<(String, bool)>,
129) -> Vec<Html> {
130 specs
131 .iter()
132 .filter_map(|spec| match spec {
133 ControlSpec::Group { key, fields } => {
134 let children =
135 render_specs(fields, raw_config, on_change, presentation, on_group_toggle);
136
137 (!children.is_empty()).then(|| {
138 html! {
139 <ControlGroup
140 key={format!("group::{key}")}
141 group_key={key.clone()}
142 open={!presentation.is_control_group_collapsed(key)}
143 on_toggle={on_group_toggle.clone()}
144 >
145 { children }
146 </ControlGroup>
147 }
148 })
149 },
150 leaf => {
151 let key = leaf.serialized_keys().join("+");
152 let component = render_leaf(leaf, raw_config, on_change)?;
153 Some(html! { <fieldset class="style-control" {key}>{ component }</fieldset> })
154 },
155 })
156 .collect_vec()
157}
158
159fn render_leaf(
160 spec: &ControlSpec,
161 raw_config: &serde_json::Map<String, serde_json::Value>,
162 on_change: &Callback<crate::config::ColumnConfigFieldUpdate>,
163) -> Option<Html> {
164 match spec.clone() {
165 ControlSpec::Enum {
166 key,
167 variants,
168 default,
169 } => {
170 let current = raw_config
171 .get(&key)
172 .and_then(|v| v.as_str().map(|s| s.to_string()));
173 Some(html! {
174 <EnumField
175 field_key={key}
176 {variants}
177 {default}
178 {current}
179 on_change={on_change.clone()}
180 />
181 })
182 },
183 ControlSpec::Bool { key, default } => {
184 let current = raw_config.get(&key).and_then(|v| v.as_bool());
185 Some(html! {
186 <BoolField field_key={key} {default} {current} on_change={on_change.clone()} />
187 })
188 },
189 ControlSpec::Color { key, default } => {
190 let current = raw_config
191 .get(&key)
192 .and_then(|v| v.as_str().map(|s| s.to_string()));
193 Some(html! {
194 <ColorField field_key={key} {default} {current} on_change={on_change.clone()} />
195 })
196 },
197 ControlSpec::Number {
198 key,
199 default,
200 min,
201 max,
202 step,
203 include,
204 } => {
205 let current = raw_config.get(&key).and_then(|v| v.as_f64());
206 Some(html! {
207 <NumberFieldPrimitive
208 field_key={key}
209 {default}
210 {current}
211 {min}
212 {max}
213 {step}
214 {include}
215 on_change={on_change.clone()}
216 />
217 })
218 },
219 ControlSpec::Group { .. }
220 | ControlSpec::AggregateDepth
221 | ControlSpec::NumberSeriesStyle { .. }
222 | ControlSpec::DatetimeFormat { .. }
223 | ControlSpec::StringFormat
224 | ControlSpec::Symbols { .. }
225 | ControlSpec::NumberFormat { .. }
226 | ControlSpec::String { .. }
227 | ControlSpec::Palette { .. }
228 | ControlSpec::GradientStops { .. } => None,
229 }
230}