Flat file database editor and browser with web interface
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.
 
 
rocket-inv/src/store/form.rs

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
}