mod agg_depth_selector;
pub(crate) mod primitive_field;
mod symbol;
use std::collections::HashMap;
use itertools::Itertools;
use perspective_client::config::ColumnType;
use yew::{Html, Properties, function_component, html};
use self::agg_depth_selector::*;
use self::primitive_field::{
BoolField, ColorField, ColorRangeField, EnumField, NumberFieldPrimitive,
};
use crate::components::column_settings_sidebar::style_tab::symbol::SymbolStyle;
use crate::components::datetime_column_style::DatetimeColumnStyle;
use crate::components::number_series_style::NumberSeriesStyle;
use crate::components::string_column_style::StringColumnStyle;
use crate::components::style_controls::CustomNumberFormat;
use crate::config::{
ControlSpec, CustomNumberFormatConfig, DatetimeColumnStyleConfig, NumberSeriesStyleConfig,
StringColumnStyleConfig,
};
use crate::presentation::Presentation;
use crate::queries::{fetch_column_abs_max, get_column_config_schema};
use crate::renderer::Renderer;
use crate::session::Session;
use crate::tasks::send_column_config;
use crate::utils::PtrEqRc;
#[derive(Clone, PartialEq, Properties)]
pub struct StyleTabProps {
pub ty: Option<ColumnType>,
pub column_name: String,
pub group_by_depth: u32,
pub view_config: PtrEqRc<perspective_client::config::ViewConfig>,
pub metadata: PtrEqRc<crate::session::SessionMetadata>,
pub column_stats: PtrEqRc<HashMap<String, crate::session::ColumnStats>>,
pub selected_theme: Option<String>,
pub presentation: Presentation,
pub renderer: Renderer,
pub session: Session,
}
#[function_component]
pub fn StyleTab(props: &StyleTabProps) -> Html {
let revision = yew::use_state(|| 0u32);
let abs_max = props
.column_stats
.get(&props.column_name)
.and_then(|s| s.abs_max);
yew::use_effect_with((props.column_name.clone(), props.view_config.clone()), {
let session = props.session.clone();
let column_name = props.column_name.clone();
move |_| {
fetch_column_abs_max(&session, column_name);
|| ()
}
});
let raw_config = props.renderer.get_columns_config(&props.column_name);
let on_change = {
let state = props.clone();
let column_name = props.column_name.clone();
let revision = revision.clone();
yew::Callback::from(move |config: crate::config::ColumnConfigFieldUpdate| {
send_column_config(&state.session, &state.renderer, &column_name, config);
revision.set(*revision + 1);
})
};
fn deser_sub<T: serde::de::DeserializeOwned>(
raw: &Option<serde_json::Map<String, serde_json::Value>>,
) -> Option<T> {
raw.as_ref()
.and_then(|m| serde_json::from_value::<T>(serde_json::Value::Object(m.clone())).ok())
}
let components = get_column_config_schema(
&props.renderer,
&props.view_config,
&props.metadata,
&props.column_name,
raw_config.as_ref(),
abs_max,
)
.map(|schema| {
schema
.fields
.into_iter()
.filter_map(|spec| {
let keys: Vec<String> = spec
.serialized_keys()
.into_iter()
.map(|s| s.to_string())
.collect();
let component = match spec {
ControlSpec::AggregateDepth => {
let aggregate_depth = raw_config
.as_ref()
.and_then(|m| m.get("aggregate_depth"))
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32;
html! {
<AggregateDepthSelector
group_by_depth={props.group_by_depth}
on_change={on_change.clone()}
column_name={props.column_name.to_owned()}
value={aggregate_depth}
keys={keys.clone()}
/>
}
},
ControlSpec::NumberSeriesStyle {
default: default_config,
} => {
let config: Option<NumberSeriesStyleConfig> = deser_sub(&raw_config);
html! {
<NumberSeriesStyle
{config}
{default_config}
on_change={on_change.clone()}
keys={keys.clone()}
/>
}
},
ControlSpec::DatetimeFormat => {
let config: Option<DatetimeColumnStyleConfig> = deser_sub(&raw_config);
let enable_time_config = props.ty.unwrap() == ColumnType::Datetime;
html! {
<DatetimeColumnStyle
{enable_time_config}
{config}
on_change={on_change.clone()}
keys={keys.clone()}
/>
}
},
ControlSpec::StringFormat => {
let config: Option<StringColumnStyleConfig> = deser_sub(&raw_config);
html! {
<StringColumnStyle
{config}
on_change={on_change.clone()}
keys={keys.clone()}
/>
}
},
ControlSpec::Symbols {
default: default_config,
} => {
let restored_config: HashMap<String, String> = raw_config
.as_ref()
.and_then(|m| m.get("symbols"))
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
html! {
<SymbolStyle
{default_config}
restored_config={Some(restored_config)}
on_change={on_change.clone()}
column_name={props.column_name.clone()}
selected_theme={props.selected_theme.clone()}
session={props.session.clone()}
keys={keys.clone()}
/>
}
},
ControlSpec::NumberFormat => {
let restored_config: CustomNumberFormatConfig = raw_config
.as_ref()
.and_then(|m| m.get("number_format"))
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
html! {
<CustomNumberFormat
{restored_config}
on_change={on_change.clone()}
view_type={props.ty.unwrap()}
column_name={props.column_name.clone()}
keys={keys.clone()}
/>
}
},
ControlSpec::Enum {
key,
variants,
default,
} => {
let current = raw_config
.as_ref()
.and_then(|m| m.get(&key))
.and_then(|v| v.as_str().map(|s| s.to_string()));
html! {
<EnumField
field_key={key}
{variants}
{default}
{current}
on_change={on_change.clone()}
/>
}
},
ControlSpec::Bool { key, default } => {
let current = raw_config
.as_ref()
.and_then(|m| m.get(&key))
.and_then(|v| v.as_bool());
html! {
<BoolField
field_key={key}
{default}
{current}
on_change={on_change.clone()}
/>
}
},
ControlSpec::Color { key, default } => {
let current = raw_config
.as_ref()
.and_then(|m| m.get(&key))
.and_then(|v| v.as_str().map(|s| s.to_string()));
html! {
<ColorField
field_key={key}
{default}
{current}
on_change={on_change.clone()}
/>
}
},
ControlSpec::ColorRange {
key_pos,
key_neg,
default_pos,
default_neg,
is_gradient,
} => {
let current_pos = raw_config
.as_ref()
.and_then(|m| m.get(&key_pos))
.and_then(|v| v.as_str().map(|s| s.to_string()));
let current_neg = raw_config
.as_ref()
.and_then(|m| m.get(&key_neg))
.and_then(|v| v.as_str().map(|s| s.to_string()));
html! {
<ColorRangeField
field_key_pos={key_pos}
field_key_neg={key_neg}
{default_pos}
{default_neg}
{current_pos}
{current_neg}
{is_gradient}
on_change={on_change.clone()}
/>
}
},
ControlSpec::Number {
key,
default,
min,
max,
step,
include,
} => {
let current = raw_config
.as_ref()
.and_then(|m| m.get(&key))
.and_then(|v| v.as_f64());
html! {
<NumberFieldPrimitive
field_key={key}
{default}
{current}
{min}
{max}
{step}
{include}
on_change={on_change.clone()}
/>
}
},
ControlSpec::String { .. } => {
return None;
},
};
Some(html! { <fieldset class="style-control">{ component }</fieldset> })
})
.collect_vec()
})
.unwrap_or_else(|error| {
tracing::error!("{}", error);
vec![]
});
html! {
<div id="style-tab">
<div id="column-style-container" class="tab-section">{ components }</div>
</div>
}
}