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::alignment_field::AlignmentField;
23use crate::components::column_settings_sidebar::style_tab::font_field::FontField;
24use crate::components::column_settings_sidebar::style_tab::primitive_field::{
25 BoolField, ColorField, EnumField, NumberFieldPrimitive,
26};
27use crate::config::{Alignment, ControlSpec, FontToggle};
28use crate::presentation::Presentation;
29use crate::queries::get_plugin_config_schema;
30use crate::renderer::Renderer;
31use crate::session::Session;
32use crate::tasks::send_plugin_config;
33use crate::ui::ControlGroup;
34use crate::utils::PtrEqRc;
35
36#[derive(Clone, PartialEq, Properties)]
37pub struct PluginTabProps {
38 pub view_config: PtrEqRc<ViewConfig>,
41
42 pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
52
53 pub presentation: Presentation,
55 pub renderer: Renderer,
56 pub session: Session,
57}
58
59#[function_component]
60pub fn PluginTab(props: &PluginTabProps) -> Html {
61 let schema = {
75 let renderer = props.renderer.clone();
76 let view_config = props.view_config.clone();
77 let plugin_config = props.plugin_config.clone();
78 use_memo(
79 (props.plugin_config.clone(), props.view_config.clone()),
80 move |_| match get_plugin_config_schema(&renderer, &view_config, Some(&plugin_config)) {
81 Ok(schema) => schema.fields,
82 Err(error) => {
83 tracing::error!("{}", error);
84 vec![]
85 },
86 },
87 )
88 };
89
90 let on_change = {
91 let session = props.session.clone();
92 let renderer = props.renderer.clone();
93 yew::Callback::from(move |update: crate::config::ColumnConfigFieldUpdate| {
94 send_plugin_config(&session, &renderer, update);
101 })
102 };
103
104 let on_group_toggle = {
105 let presentation = props.presentation.clone();
106 yew::Callback::from(move |(key, open): (String, bool)| {
107 presentation.set_control_group_collapsed(&key, !open);
108 })
109 };
110
111 let raw_config = &*props.plugin_config;
112 let components = render_specs(
113 &schema,
114 raw_config,
115 &on_change,
116 &props.presentation,
117 &on_group_toggle,
118 );
119 html! {
120 <div id="plugin-tab" class="sidebar_column scrollable">
121 <div id="plugin-config-container" class="tab-section">{ components }</div>
122 </div>
123 }
124}
125
126fn render_specs(
127 specs: &[ControlSpec],
128 raw_config: &serde_json::Map<String, serde_json::Value>,
129 on_change: &Callback<crate::config::ColumnConfigFieldUpdate>,
130 presentation: &Presentation,
131 on_group_toggle: &Callback<(String, bool)>,
132) -> Vec<Html> {
133 specs
134 .iter()
135 .filter_map(|spec| match spec {
136 ControlSpec::Group { key, fields } => {
137 let children =
138 render_specs(fields, raw_config, on_change, presentation, on_group_toggle);
139
140 (!children.is_empty()).then(|| {
141 html! {
142 <ControlGroup
143 key={format!("group::{key}")}
144 group_key={key.clone()}
145 open={!presentation.is_control_group_collapsed(key)}
146 on_toggle={on_group_toggle.clone()}
147 >
148 { children }
149 </ControlGroup>
150 }
151 })
152 },
153 leaf => {
154 let key = leaf.serialized_keys().join("+");
155 let component = render_leaf(leaf, raw_config, on_change)?;
156 Some(html! { <fieldset class="style-control" {key}>{ component }</fieldset> })
157 },
158 })
159 .collect_vec()
160}
161
162fn render_leaf(
163 spec: &ControlSpec,
164 raw_config: &serde_json::Map<String, serde_json::Value>,
165 on_change: &Callback<crate::config::ColumnConfigFieldUpdate>,
166) -> Option<Html> {
167 match spec.clone() {
168 ControlSpec::Enum {
169 key,
170 variants,
171 default,
172 } => {
173 let current = raw_config
174 .get(&key)
175 .and_then(|v| v.as_str().map(|s| s.to_string()));
176 Some(html! {
177 <EnumField
178 field_key={key}
179 {variants}
180 {default}
181 {current}
182 on_change={on_change.clone()}
183 />
184 })
185 },
186 ControlSpec::Font {
187 key,
188 default,
189 size,
190 bold,
191 italic,
192 } => {
193 let current = raw_config
194 .get(&key)
195 .and_then(|v| v.as_str().map(|s| s.to_string()));
196 let toggle = |t: &FontToggle| raw_config.get(&t.key).and_then(|v| v.as_bool());
197 let size_current = size
198 .as_ref()
199 .and_then(|s| raw_config.get(&s.key))
200 .and_then(|v| v.as_f64());
201 let bold_current = bold.as_ref().and_then(toggle);
202 let italic_current = italic.as_ref().and_then(toggle);
203 Some(html! {
204 <FontField
205 field_key={key}
206 {default}
207 {current}
208 {size}
209 {size_current}
210 {bold}
211 {bold_current}
212 {italic}
213 {italic_current}
214 on_change={on_change.clone()}
215 />
216 })
217 },
218 ControlSpec::Alignment {
219 key,
220 default,
221 corners,
222 } => {
223 let current = raw_config
224 .get(&key)
225 .and_then(|v| v.as_str())
226 .and_then(Alignment::parse);
227
228 Some(html! {
229 <AlignmentField
230 field_key={key}
231 {default}
232 {corners}
233 {current}
234 on_change={on_change.clone()}
235 />
236 })
237 },
238 ControlSpec::Bool { key, default } => {
239 let current = raw_config.get(&key).and_then(|v| v.as_bool());
240 Some(html! {
241 <BoolField field_key={key} {default} {current} on_change={on_change.clone()} />
242 })
243 },
244 ControlSpec::Color { key, default } => {
245 let current = raw_config
246 .get(&key)
247 .and_then(|v| v.as_str().map(|s| s.to_string()));
248 Some(html! {
249 <ColorField field_key={key} {default} {current} on_change={on_change.clone()} />
250 })
251 },
252 ControlSpec::Number {
253 key,
254 default,
255 min,
256 max,
257 step,
258 include,
259 } => {
260 let current = raw_config.get(&key).and_then(|v| v.as_f64());
261 Some(html! {
262 <NumberFieldPrimitive
263 field_key={key}
264 {default}
265 {current}
266 {min}
267 {max}
268 {step}
269 {include}
270 on_change={on_change.clone()}
271 />
272 })
273 },
274 ControlSpec::Group { .. }
275 | ControlSpec::AggregateDepth
276 | ControlSpec::NumberSeriesStyle { .. }
277 | ControlSpec::DatetimeFormat { .. }
278 | ControlSpec::Symbols { .. }
279 | ControlSpec::NumberFormat { .. }
280 | ControlSpec::String { .. }
281 | ControlSpec::Palette { .. }
282 | ControlSpec::GradientStops { .. } => None,
283 }
284}