You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
10 KiB
317 lines
10 KiB
use crate::store::model::FieldKind;
|
|
use crate::store::{model, Indexes, Store};
|
|
use serde_json::Value;
|
|
use std::borrow::Cow;
|
|
use std::collections::BTreeSet;
|
|
|
|
use lazy_static::lazy_static;
|
|
use indexmap::map::IndexMap;
|
|
use rocket::request::{FormItems, FromForm};
|
|
|
|
lazy_static! {
|
|
/// This is an example for using doc comment attributes
|
|
static ref EMPTY_SET: BTreeSet<String> = Default::default();
|
|
}
|
|
|
|
#[derive(Serialize, Debug, Default)]
|
|
pub struct RenderedField<'a> {
|
|
pub key: Cow<'a, str>,
|
|
pub label: Cow<'a, str>,
|
|
pub kind: &'static str,
|
|
pub step: &'static str,
|
|
pub min: String,
|
|
pub max: String,
|
|
pub all_tags_json: String,
|
|
pub tags_json: String,
|
|
pub options: Option<Vec<&'a String>>,
|
|
pub value: Cow<'a, str>,
|
|
pub checked: bool,
|
|
}
|
|
|
|
impl<'a> RenderedField<'a> {
|
|
pub fn from_template_field<'i>(
|
|
key: &'i String,
|
|
field: &'i model::Field,
|
|
value: Option<&'i Value>,
|
|
index: &'i Indexes,
|
|
) -> RenderedField<'i> {
|
|
let mut rendered = RenderedField::default();
|
|
rendered.key = key.as_str().into();
|
|
rendered.label = if field.label.is_empty() {
|
|
titlecase::titlecase(&key.replace('_', " ")).into()
|
|
} else {
|
|
field.label.as_str().into()
|
|
};
|
|
|
|
match &field.kind {
|
|
FieldKind::String => {
|
|
rendered.kind = "string";
|
|
}
|
|
FieldKind::Text => {
|
|
rendered.kind = "text";
|
|
}
|
|
FieldKind::Bool { default } => {
|
|
rendered.kind = "bool";
|
|
|
|
rendered.checked = if let Some(Value::Bool(v)) = value {
|
|
*v
|
|
} else {
|
|
*default
|
|
}
|
|
}
|
|
FieldKind::Int { min, max, default } => {
|
|
rendered.kind = "number";
|
|
|
|
let num = if let Some(Value::Number(n)) = value {
|
|
n.as_i64().expect("Error parsing number")
|
|
} else {
|
|
*default
|
|
};
|
|
|
|
if let Some(n) = min {
|
|
rendered.min = n.to_string();
|
|
}
|
|
|
|
if let Some(n) = max {
|
|
rendered.max = n.to_string();
|
|
}
|
|
|
|
rendered.value = Cow::Owned(num.to_string());
|
|
rendered.step = "1";
|
|
}
|
|
FieldKind::Float { min, max, default } => {
|
|
rendered.kind = "number";
|
|
|
|
let num = if let Some(Value::Number(n)) = value {
|
|
n.as_f64().expect("Error parsing number")
|
|
} else {
|
|
*default
|
|
};
|
|
|
|
if let Some(n) = min {
|
|
rendered.min = n.to_string();
|
|
}
|
|
|
|
if let Some(n) = max {
|
|
rendered.max = n.to_string();
|
|
}
|
|
|
|
rendered.value = Cow::Owned(num.to_string());
|
|
rendered.step = "any";
|
|
}
|
|
FieldKind::Enum { options, default } => {
|
|
rendered.kind = "select";
|
|
rendered.options = Some(options.iter().collect());
|
|
|
|
if let Some(Value::String(s)) = value {
|
|
rendered.value = Cow::Borrowed(&s.as_str());
|
|
} else if let Some(def) = default {
|
|
rendered.value = def.to_owned().into();
|
|
}
|
|
}
|
|
FieldKind::FreeEnum { enum_group } => {
|
|
rendered.kind = "free_select";
|
|
let group = enum_group.as_ref().unwrap_or(key);
|
|
let options = index.free_enums.get(group).unwrap_or(&EMPTY_SET);
|
|
rendered.options = Some(options.iter().collect());
|
|
}
|
|
FieldKind::Tags { options } => {
|
|
rendered.kind = "tags";
|
|
rendered.options = Some(options.iter().collect());
|
|
|
|
rendered.all_tags_json = serde_json::to_string(options).unwrap();
|
|
}
|
|
FieldKind::FreeTags { tag_group } => {
|
|
rendered.kind = "free_tags";
|
|
let group = tag_group.as_ref().unwrap_or(key);
|
|
let options = index.free_tags.get(group).unwrap_or(&EMPTY_SET);
|
|
|
|
rendered.options = Some(options.iter().collect());
|
|
rendered.all_tags_json = "[]".into();
|
|
}
|
|
}
|
|
|
|
// Shared code by multiple variants
|
|
match field.kind {
|
|
FieldKind::Text | FieldKind::String => {
|
|
if let Some(Value::String(s)) = value {
|
|
rendered.value = Cow::Borrowed(&s.as_str());
|
|
}
|
|
}
|
|
FieldKind::Enum { .. } | FieldKind::FreeEnum { .. } => {
|
|
if let Some(Value::String(s)) = value {
|
|
rendered.value = Cow::Borrowed(&s.as_str());
|
|
}
|
|
}
|
|
FieldKind::Tags { .. } | FieldKind::FreeTags { .. } => {
|
|
if let Some(v) = value {
|
|
rendered.value = serde_json::from_value::<Vec<String>>(v.clone())
|
|
.unwrap()
|
|
.join(" ")
|
|
.into();
|
|
rendered.tags_json = serde_json::to_string(v).unwrap();
|
|
} else {
|
|
rendered.tags_json = "[]".into();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
rendered
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct RenderedCard<'a> {
|
|
pub fields: Vec<RenderedField<'a>>,
|
|
pub id: usize,
|
|
}
|
|
|
|
pub fn render_empty_fields(store: &Store) -> Vec<RenderedField> {
|
|
let indexes = &store.index;
|
|
|
|
store
|
|
.model
|
|
.fields
|
|
.iter()
|
|
.map(|(key, field)| RenderedField::from_template_field(key, field, None, indexes))
|
|
.collect()
|
|
}
|
|
|
|
pub fn render_card_fields<'a>(
|
|
store: &'a Store,
|
|
values: &'a serde_json::Map<String, Value>,
|
|
) -> Vec<RenderedField<'a>> {
|
|
let indexes = &store.index;
|
|
|
|
store
|
|
.model
|
|
.fields
|
|
.iter()
|
|
.map(|(key, field)| {
|
|
RenderedField::from_template_field(key, field, values.get(key), indexes)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
|
|
#[derive(Default)]
|
|
pub struct MapFromForm {
|
|
pub data: IndexMap<String, String>,
|
|
}
|
|
|
|
impl<'a> FromForm<'a> for MapFromForm {
|
|
type Error = ();
|
|
|
|
fn from_form(items: &mut FormItems, _strict: bool) -> Result<Self, Self::Error> {
|
|
let mut new = MapFromForm::default();
|
|
items.for_each(|item| {
|
|
let (k, v) = item.key_value_decoded();
|
|
new.data.insert(k, v);
|
|
});
|
|
Ok(new)
|
|
}
|
|
}
|
|
|
|
pub fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap<String, Value> {
|
|
let mut card = IndexMap::new();
|
|
|
|
for (k, field) in &store.model.fields {
|
|
let mut value: Option<Value> = None;
|
|
if let Some(input) = form.data.remove(k) {
|
|
value = Some(match &field.kind {
|
|
FieldKind::Text | FieldKind::String | FieldKind::FreeEnum { .. } => {
|
|
Value::String(input)
|
|
}
|
|
FieldKind::Bool { .. } => serde_json::to_value(true).unwrap(),
|
|
FieldKind::Int { min, max, default } => {
|
|
let mut val: i64 = if input.is_empty() {
|
|
*default
|
|
} else {
|
|
input.parse().expect("Error parse number")
|
|
};
|
|
|
|
if let Some(min) = min {
|
|
val = val.max(*min);
|
|
}
|
|
|
|
if let Some(max) = max {
|
|
val = val.min(*max);
|
|
}
|
|
|
|
serde_json::to_value(val).unwrap()
|
|
}
|
|
FieldKind::Float { min, max, default } => {
|
|
let mut val: f64 = if input.is_empty() {
|
|
*default
|
|
} else {
|
|
input.parse().expect("Error parse number")
|
|
};
|
|
|
|
if let Some(min) = min {
|
|
val = val.min(*min);
|
|
}
|
|
|
|
if let Some(max) = max {
|
|
val = val.max(*max);
|
|
}
|
|
|
|
serde_json::to_value(val).unwrap()
|
|
}
|
|
FieldKind::Enum { options, default } => {
|
|
if options.contains(&input) {
|
|
Value::String(input)
|
|
} else {
|
|
let val = default.as_ref().map(ToOwned::to_owned).unwrap_or_else(|| {
|
|
options
|
|
.first()
|
|
.expect("fixed enum must have values")
|
|
.to_owned()
|
|
});
|
|
|
|
Value::String(val)
|
|
}
|
|
}
|
|
FieldKind::Tags { options } => {
|
|
let mut tags: Vec<String> = input
|
|
.split(' ')
|
|
.map(ToOwned::to_owned)
|
|
.filter_map(|tag| {
|
|
if options.contains(&tag) {
|
|
Some(tag)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
tags.sort();
|
|
|
|
serde_json::to_value(tags).unwrap()
|
|
}
|
|
FieldKind::FreeTags { .. } => {
|
|
let mut tags: Vec<String> = input
|
|
.split(' ')
|
|
.map(str::trim)
|
|
.filter(|s| !s.is_empty())
|
|
.map(ToOwned::to_owned)
|
|
.collect();
|
|
|
|
tags.sort();
|
|
|
|
serde_json::to_value(tags).unwrap()
|
|
}
|
|
});
|
|
} else {
|
|
if let FieldKind::Bool { .. } = field.kind {
|
|
value = Some(Value::Bool(false));
|
|
}
|
|
}
|
|
|
|
if let Some(v) = value {
|
|
card.insert(k.to_owned(), v);
|
|
}
|
|
}
|
|
|
|
card
|
|
}
|
|
|