use serde::{Deserialize, Serialize};
use vize_carton::FxHashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ControlKind {
Text,
Number,
Boolean,
Range,
Select,
Radio,
Color,
Date,
Object,
Array,
File,
Raw,
}
impl Default for ControlKind {
#[inline]
fn default() -> Self {
Self::Text
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RangeConfig {
pub min: f64,
pub max: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub step: Option<f64>,
}
impl Default for RangeConfig {
#[inline]
fn default() -> Self {
Self {
min: 0.0,
max: 100.0,
step: Some(1.0),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SelectOption {
pub label: String,
pub value: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PropControl {
pub name: String,
pub control: ControlKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_value: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub required: bool,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub options: Vec<SelectOption>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<RangeConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
}
impl PropControl {
#[inline]
pub fn text(name: impl Into<String>) -> Self {
Self {
name: name.into(),
control: ControlKind::Text,
default_value: None,
description: None,
required: false,
options: Vec::new(),
range: None,
group: None,
}
}
#[inline]
pub fn number(name: impl Into<String>) -> Self {
Self {
name: name.into(),
control: ControlKind::Number,
default_value: None,
description: None,
required: false,
options: Vec::new(),
range: None,
group: None,
}
}
#[inline]
pub fn boolean(name: impl Into<String>) -> Self {
Self {
name: name.into(),
control: ControlKind::Boolean,
default_value: None,
description: None,
required: false,
options: Vec::new(),
range: None,
group: None,
}
}
#[inline]
pub fn select(name: impl Into<String>, options: Vec<SelectOption>) -> Self {
Self {
name: name.into(),
control: ControlKind::Select,
default_value: None,
description: None,
required: false,
options,
range: None,
group: None,
}
}
#[inline]
pub fn range(name: impl Into<String>, config: RangeConfig) -> Self {
Self {
name: name.into(),
control: ControlKind::Range,
default_value: None,
description: None,
required: false,
options: Vec::new(),
range: Some(config),
group: None,
}
}
#[inline]
pub fn with_default(mut self, value: serde_json::Value) -> Self {
self.default_value = Some(value);
self
}
#[inline]
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
#[inline]
pub fn required(mut self) -> Self {
self.required = true;
self
}
#[inline]
pub fn with_group(mut self, group: impl Into<String>) -> Self {
self.group = Some(group.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Palette {
pub title: String,
pub controls: Vec<PropControl>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub groups: Vec<String>,
#[serde(skip_serializing_if = "FxHashMap::is_empty", default)]
pub all_values: FxHashMap<String, Vec<serde_json::Value>>,
}
impl Palette {
#[inline]
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
controls: Vec::new(),
groups: Vec::new(),
all_values: FxHashMap::default(),
}
}
#[inline]
pub fn add_control(&mut self, control: PropControl) {
if let Some(ref group) = control.group {
if !self.groups.contains(group) {
self.groups.push(group.clone());
}
}
self.controls.push(control);
}
pub fn controls_by_group(&self, group: Option<&str>) -> Vec<&PropControl> {
self.controls
.iter()
.filter(|c| c.group.as_deref() == group)
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaletteOptions {
#[serde(default = "default_true")]
pub infer_options: bool,
#[serde(default = "default_min_select")]
pub min_select_values: usize,
#[serde(default = "default_max_select")]
pub max_select_values: usize,
#[serde(default)]
pub group_by_type: bool,
}
impl Default for PaletteOptions {
#[inline]
fn default() -> Self {
Self {
infer_options: true,
min_select_values: 2,
max_select_values: 10,
group_by_type: false,
}
}
}
fn default_true() -> bool {
true
}
fn default_min_select() -> usize {
2
}
fn default_max_select() -> usize {
10
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaletteOutput {
pub palette: Palette,
pub json: String,
pub typescript: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_builders() {
let text = PropControl::text("label")
.with_default(serde_json::json!("Hello"))
.with_description("Button label")
.required();
assert_eq!(text.name, "label");
assert_eq!(text.control, ControlKind::Text);
assert!(text.required);
assert_eq!(text.default_value, Some(serde_json::json!("Hello")));
}
#[test]
fn test_select_control() {
let options = vec![
SelectOption {
label: "Small".to_string(),
value: serde_json::json!("sm"),
},
SelectOption {
label: "Medium".to_string(),
value: serde_json::json!("md"),
},
SelectOption {
label: "Large".to_string(),
value: serde_json::json!("lg"),
},
];
let select = PropControl::select("size", options);
assert_eq!(select.control, ControlKind::Select);
assert_eq!(select.options.len(), 3);
}
#[test]
fn test_palette_groups() {
let mut palette = Palette::new("Button");
palette.add_control(PropControl::text("label").with_group("Content"));
palette.add_control(PropControl::text("icon").with_group("Content"));
palette.add_control(PropControl::boolean("disabled").with_group("State"));
assert_eq!(palette.groups, vec!["Content", "State"]);
assert_eq!(palette.controls_by_group(Some("Content")).len(), 2);
assert_eq!(palette.controls_by_group(Some("State")).len(), 1);
}
}