build form from yaml
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
.idea/
|
||||
inventory.json
|
||||
|
||||
|
||||
Generated
+71
@@ -174,6 +174,11 @@ dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "educe"
|
||||
version = "0.4.2"
|
||||
@@ -304,6 +309,28 @@ dependencies = [
|
||||
"unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ifmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ifmt-impl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ifmt-impl"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.3.0"
|
||||
@@ -382,6 +409,11 @@ name = "libc"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.3.2"
|
||||
@@ -633,6 +665,16 @@ dependencies = [
|
||||
"sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.4.30"
|
||||
@@ -733,13 +775,16 @@ dependencies = [
|
||||
name = "rocket-inv"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ifmt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"json_dotpath 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rocket-download-response 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rocket_contrib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -841,6 +886,17 @@ dependencies = [
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.1"
|
||||
@@ -1136,6 +1192,14 @@ dependencies = [
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.4.0"
|
||||
@@ -1169,6 +1233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum devise_codegen 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "066ceb7928ca93a9bedc6d0e612a8a0424048b0ab1f75971b203d01420c055d7"
|
||||
"checksum devise_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf41c59b22b5e3ec0ea55c7847e5f358d340f3a8d6d53a5cf4f1564967f96487"
|
||||
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
|
||||
"checksum educe 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d27c760a73a13abb1ddf417c06bc2b8d14b0607dd19097316e0ca9412a971088"
|
||||
"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
@@ -1185,6 +1250,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
|
||||
"checksum hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
|
||||
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
|
||||
"checksum ifmt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "abec215007c2ef1ccfb17a6bae6a87736fedb573860d2606ddec08b427666164"
|
||||
"checksum ifmt-impl 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "744691ef283c5d8d4321f75cfa0e3b460b5adf3338bb7dcfd060f33e40300072"
|
||||
"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2"
|
||||
"checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
|
||||
"checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
|
||||
@@ -1196,6 +1263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
|
||||
"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586"
|
||||
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
@@ -1224,6 +1292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||
"checksum pest_generator 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0"
|
||||
"checksum pest_meta 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df43fd99896fd72c485fe47542c7b500e4ac1e8700bf995544d1317a60ded547"
|
||||
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
|
||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
@@ -1245,6 +1314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
|
||||
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
|
||||
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
|
||||
"checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35"
|
||||
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
|
||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
"checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
|
||||
@@ -1285,5 +1355,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"
|
||||
"checksum yansi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d60c3b48c9cdec42fb06b3b84b5b087405e1fa1c644a1af3930e4dfafe93de48"
|
||||
"checksum yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
|
||||
|
||||
@@ -12,9 +12,12 @@ rocket-download-response = "0.4.9"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8.11"
|
||||
json_dotpath = "0.1.2"
|
||||
|
||||
ifmt = "0.2.0"
|
||||
parking_lot = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[dependencies.rocket]
|
||||
version = "0.4.2"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
model:
|
||||
fields:
|
||||
category:
|
||||
type: "free_enum"
|
||||
generic_code:
|
||||
type: "free_enum"
|
||||
code:
|
||||
type: "string"
|
||||
value:
|
||||
type: "string"
|
||||
package:
|
||||
type: "free_enum"
|
||||
mounting:
|
||||
type: "enum"
|
||||
options:
|
||||
- "SMD"
|
||||
- "Through-hole"
|
||||
- "Screw"
|
||||
quantity:
|
||||
type: "int"
|
||||
min: 0
|
||||
location:
|
||||
type: "free_enum"
|
||||
sublocation:
|
||||
label: "Sub-location"
|
||||
type: "string"
|
||||
tags:
|
||||
type: "free_tags"
|
||||
note:
|
||||
type: "text"
|
||||
checkbox:
|
||||
type: "bool"
|
||||
+28
-10
@@ -5,7 +5,7 @@
|
||||
|
||||
//use rocket::request::FromSegments;
|
||||
//use rocket::http::uri::Segments;
|
||||
//use rocket_contrib::serve::StaticFiles;
|
||||
use rocket_contrib::serve::StaticFiles;
|
||||
use rocket_contrib::templates::Template;
|
||||
|
||||
mod store;
|
||||
@@ -16,24 +16,42 @@ use rocket::response::Redirect;
|
||||
use rocket::http::Status;
|
||||
use rocket::request::Form;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use crate::store::form::RenderedField;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FormContext<'a> {
|
||||
pub fields : Vec<RenderedField<'a>>,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index(store : State<RwLock<Store>>) -> Template {
|
||||
let mut context = HashMap::new();
|
||||
let rg = store.read();
|
||||
context.insert("records", &rg.parts);
|
||||
|
||||
let indexes = &rg.index;
|
||||
let context = FormContext {
|
||||
fields: rg.model.fields.iter().map(|(key, field)| {
|
||||
RenderedField::from_template_field(key, field, None, indexes)
|
||||
}).collect()
|
||||
};
|
||||
|
||||
Template::render("index", context)
|
||||
}
|
||||
|
||||
#[post("/add", data="<record>")]
|
||||
fn add_part(store : State<RwLock<Store>>, record : Form<store::Part>) -> Redirect {
|
||||
store.write().add(record.into_inner());
|
||||
Redirect::to(uri!(index))
|
||||
}
|
||||
//#[post("/add", data="<record>")]
|
||||
//fn add_part(store : State<RwLock<Store>>, record : Form<store::Part>) -> Redirect {
|
||||
// store.write().add(record.into_inner());
|
||||
// Redirect::to(uri!(index))
|
||||
//}
|
||||
|
||||
fn main() {
|
||||
let cwd = env::current_dir().unwrap();
|
||||
let data_dir = cwd.join("data");
|
||||
let store = Store::new(data_dir);
|
||||
|
||||
rocket::ignite()
|
||||
.attach(Template::fairing())
|
||||
.manage(RwLock::new(Store::new()))
|
||||
.mount("/", routes![index, add_part]).launch();
|
||||
.manage(RwLock::new(store))
|
||||
.mount("/", StaticFiles::from(cwd.join("templates/static/")))
|
||||
.mount("/", routes![index]).launch();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
use crate::store::model::FieldKind;
|
||||
use serde_json::Value;
|
||||
use std::borrow::Cow;
|
||||
use crate::store::{model, Indexes};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
/// This is an example for using doc comment attributes
|
||||
static ref EMPTY_VEC: Vec<String> = vec![];
|
||||
}
|
||||
|
||||
#[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 options: Option<&'a Vec<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() {
|
||||
rendered.key.clone()
|
||||
} else {
|
||||
field.label.as_str().into()
|
||||
};
|
||||
|
||||
match &field.kind {
|
||||
FieldKind::String => {
|
||||
rendered.kind = "string";
|
||||
|
||||
if let Some(Value::String(s)) = value {
|
||||
rendered.value = Cow::Borrowed(&s.as_str());
|
||||
}
|
||||
}
|
||||
FieldKind::Text => {
|
||||
rendered.kind = "text";
|
||||
|
||||
if let Some(Value::String(s)) = value {
|
||||
rendered.value = Cow::Borrowed(&s.as_str());
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
FieldKind::FreeEnum { enum_group } => {
|
||||
rendered.kind = "free_select";
|
||||
let group = enum_group.as_ref().unwrap_or(key);
|
||||
rendered.options = Some(index.free_enums.get(group).unwrap_or(&EMPTY_VEC))
|
||||
}
|
||||
FieldKind::Tags { options } => {
|
||||
rendered.kind = "select";
|
||||
rendered.options = Some(options);
|
||||
}
|
||||
FieldKind::FreeTags { tag_group } => {
|
||||
rendered.kind = "free_select";
|
||||
let group = tag_group.as_ref().unwrap_or(key);
|
||||
rendered.options = Some(index.free_tags.get(group).unwrap_or(&EMPTY_VEC))
|
||||
}
|
||||
}
|
||||
|
||||
rendered
|
||||
}
|
||||
}
|
||||
+69
-33
@@ -1,52 +1,88 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write, Error};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use rocket::request::FromForm;
|
||||
use crate::store::model::Model;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
pub mod model;
|
||||
pub mod form;
|
||||
|
||||
/// Store instance
|
||||
#[derive(Debug)]
|
||||
pub struct Store {
|
||||
pub parts : Vec<Part>
|
||||
path : PathBuf,
|
||||
pub model: Model,
|
||||
pub items : HashMap<usize, serde_json::Value>,
|
||||
pub index : Indexes,
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize,FromForm)]
|
||||
pub struct Part {
|
||||
name : String,
|
||||
quantity : usize,
|
||||
location : String,
|
||||
/// Indexes loaded from the indexes file
|
||||
#[derive(Serialize,Deserialize,Debug,Default)]
|
||||
pub struct Indexes {
|
||||
pub free_enums : HashMap<String, Vec<String>>,
|
||||
pub free_tags : HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
fn load_file_or(file : impl AsRef<Path>, def : String) -> String {
|
||||
/// Struct loaded from the repositroy config file
|
||||
#[derive(Serialize,Deserialize,Debug)]
|
||||
struct RepositoryConfig {
|
||||
pub model : Model,
|
||||
}
|
||||
|
||||
const REPO_CONFIG_FILE : &'static str = "repository.yaml";
|
||||
const REPO_DATA_FILE : &'static str = "data.json";
|
||||
const REPO_INDEX_FILE : &'static str = "index.json";
|
||||
|
||||
impl Store {
|
||||
pub fn new(path: impl AsRef<Path>) -> Self {
|
||||
let file = load_file(path.as_ref().join(REPO_CONFIG_FILE));
|
||||
|
||||
let repository_config : RepositoryConfig = serde_yaml::from_str(&file)
|
||||
.expect("Error parsing repository config file.");
|
||||
|
||||
let items = load_file_or(path.as_ref().join(REPO_DATA_FILE), "{}");
|
||||
let indexes = load_file_or(path.as_ref().join(REPO_INDEX_FILE), "{}");
|
||||
|
||||
Store {
|
||||
path: path.as_ref().into(),
|
||||
model: repository_config.model,
|
||||
items: serde_json::from_str(&items).expect("Error parsing data file."),
|
||||
index: serde_json::from_str(&indexes).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persist(&self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(self.path.join(REPO_DATA_FILE))
|
||||
.expect("Error opening data file for writing.");
|
||||
|
||||
let serialized = serde_json::to_string(&self.items).expect("Error serialize.");
|
||||
file.write(serialized.as_bytes()).expect("Error write data file");
|
||||
}
|
||||
}
|
||||
|
||||
fn load_file(path: impl AsRef<Path>) -> String {
|
||||
let mut file= File::open(&path).expect(&format!("Error opening file {}", path.as_ref().display()));
|
||||
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf).expect(&format!("Error reading file {}", path.as_ref().display()));
|
||||
buf
|
||||
}
|
||||
|
||||
fn load_file_or(file : impl AsRef<Path>, def : impl Into<String>) -> String {
|
||||
let mut file = match File::open(file) {
|
||||
Ok(file) => file,
|
||||
Err(_) => return def
|
||||
Err(_) => return def.into()
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
if file.read_to_string(&mut buf).is_err() {
|
||||
return def;
|
||||
return def.into();
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn new() -> Self {
|
||||
let mut file = load_file_or("inventory.json", "[]".to_string());
|
||||
|
||||
Store {
|
||||
parts: serde_json::from_str(&file).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, part : Part) {
|
||||
self.parts.push(part);
|
||||
|
||||
self.persist()
|
||||
}
|
||||
|
||||
pub fn persist(&self) {
|
||||
let mut file = OpenOptions::new().write(true).create(true).truncate(true).open("inventory.json").unwrap();
|
||||
|
||||
file.write(serde_json::to_string(&self.parts).unwrap().as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A data card's model.
|
||||
/// Cards of one model can be sorted, searched and filtered by their fields.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Model {
|
||||
/// Fields defined by this model
|
||||
pub fields: HashMap<String, Field>,
|
||||
}
|
||||
|
||||
/// One field of a model
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Field {
|
||||
/// Field label shown in the user interface
|
||||
#[serde(default)]
|
||||
pub label: String,
|
||||
/// Field data type
|
||||
#[serde(flatten)]
|
||||
pub kind: FieldKind,
|
||||
}
|
||||
|
||||
/// Field data type and validations
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FieldKind {
|
||||
/// Single-line text entry
|
||||
String,
|
||||
|
||||
/// Long-form text entry, can have multiple rows
|
||||
Text,
|
||||
|
||||
/// Checkbox or a toggle switch
|
||||
Bool {
|
||||
/// Default value when the model's card is created
|
||||
#[serde(default)]
|
||||
default: bool,
|
||||
},
|
||||
|
||||
/// Integer entry
|
||||
Int {
|
||||
/// Lowest allowed value
|
||||
#[serde(default)]
|
||||
min: Option<i64>,
|
||||
/// Highest allowed value
|
||||
#[serde(default)]
|
||||
max: Option<i64>,
|
||||
/// Default value
|
||||
#[serde(default)]
|
||||
default: i64,
|
||||
},
|
||||
|
||||
/// Floating point entry
|
||||
Float {
|
||||
/// Lowest allowed value
|
||||
#[serde(default)]
|
||||
min: Option<f64>,
|
||||
/// Highest allowed value
|
||||
#[serde(default)]
|
||||
max: Option<f64>,
|
||||
/// Default value
|
||||
#[serde(default)]
|
||||
default: f64,
|
||||
},
|
||||
|
||||
/// Enumeration entry with a fixed set of options
|
||||
Enum {
|
||||
/// Options to choose from, must not be empty
|
||||
options: Vec<String>,
|
||||
/// Default option (if not the first)
|
||||
#[serde(default)]
|
||||
default: Option<String>,
|
||||
},
|
||||
|
||||
/// Enum that can be freely expanded by the user
|
||||
FreeEnum {
|
||||
/// Group name.
|
||||
/// If not set, a private group for this particular field is used.
|
||||
#[serde(default)]
|
||||
enum_group: Option<String>,
|
||||
},
|
||||
|
||||
/// Tags with a fixed set of options to choose from
|
||||
Tags {
|
||||
/// Options to choose from
|
||||
options: Vec<String>,
|
||||
},
|
||||
|
||||
/// Tags that can be freely added by the user
|
||||
FreeTags {
|
||||
/// Group name.
|
||||
/// If not set, a private group for this particular field is used.
|
||||
#[serde(default)]
|
||||
tag_group: Option<String>,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{% macro text(field) %}
|
||||
<label for="field-{{field.key}}">
|
||||
{{field.label}}
|
||||
</label>
|
||||
<input id="field-{{field.key}}"
|
||||
name="{{field.key}}"
|
||||
type="text"
|
||||
value="{{field.value}}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro longtext(field) %}
|
||||
<label for="field-{{field.key}}">
|
||||
{{field.label}}
|
||||
</label>
|
||||
<textarea id="field-{{field.key}}" name="{{field.key}}">{{field.value}}</textarea>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro free_select(field) %}
|
||||
<label for="field-{{field.key}}">
|
||||
{{field.label}}
|
||||
</label>
|
||||
<input id="field-{{field.key}}"
|
||||
name="{{field.key}}"
|
||||
type="text"
|
||||
list="suggestions-{{field.key}}"
|
||||
value="{{field.value}}">
|
||||
|
||||
<datalist id="suggestions-{{field.key}}">
|
||||
{% for option in field.options %}
|
||||
<option value="{{option}}">
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro number(field) %}
|
||||
<label for="field-{{field.key}}">
|
||||
{{field.label}}
|
||||
</label>
|
||||
<input id="field-{{field.key}}"
|
||||
name="{{field.key}}"
|
||||
type="number"
|
||||
value="{{field.value}}"
|
||||
step="{{field.step}}"
|
||||
min="{{field.min}}"
|
||||
max="{{field.max}}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro checkbox(field) %}
|
||||
<label for="field-{{field.key}}">
|
||||
{{field.label}}
|
||||
</label>
|
||||
<input id="field-{{field.key}}"
|
||||
name="{{field.key}}"
|
||||
type="checkbox"
|
||||
{% if field.checked %}checked{% endif %}>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro select(field) %}
|
||||
<label for="field-{{field.key}}">
|
||||
{{field.label}}
|
||||
</label>
|
||||
<select id="field-{{field.key}}" name="{{field.key}}">
|
||||
{% for option in field.options %}
|
||||
<option value="{{option}}">{{option}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endmacro %}
|
||||
+43
-12
@@ -1,16 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
{% extends "layout" %}
|
||||
{% import "form_macros" as form %}
|
||||
|
||||
{% block title -%}
|
||||
Form
|
||||
{%- endblock title %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="/add" method="POST">
|
||||
Name: <input type="text" name="name"><br>
|
||||
Qty: <input type="number" step="1" min="0" name="quantity"><br>
|
||||
Location: <input type="text" name="location"><br>
|
||||
{% for field in fields %}
|
||||
<div class="Row">
|
||||
{% if field.kind == "string" %}
|
||||
|
||||
{{ form::text(field=field) }}
|
||||
|
||||
{% elif field.kind == "text" %}
|
||||
|
||||
{{ form::longtext(field=field) }}
|
||||
|
||||
{% elif field.kind == "number" %}
|
||||
|
||||
{{ form::number(field=field) }}
|
||||
|
||||
{% elif field.kind == "bool" %}
|
||||
|
||||
{{ form::checkbox(field=field) }}
|
||||
|
||||
{% elif field.kind == "select" %}
|
||||
|
||||
{{ form::select(field=field) }}
|
||||
|
||||
{% elif field.kind == "free_select" %}
|
||||
|
||||
{{ form::free_select(field=field) }}
|
||||
|
||||
{% else %}
|
||||
|
||||
{{ field.key }}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
|
||||
<ul>
|
||||
{% for record in records %}
|
||||
<li>
|
||||
{{ record.name }} x {{ record.quantity }} in {{ record.location }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock content %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form .Row {
|
||||
display: flex;
|
||||
padding: .25rem;
|
||||
}
|
||||
|
||||
form .Row label {
|
||||
flex-shrink: 0;
|
||||
width: 10rem;
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
padding-right: .5rem;
|
||||
}
|
||||
|
||||
form .Row input,
|
||||
form .Row select {
|
||||
height: 2.1rem;
|
||||
}
|
||||
|
||||
form .Row textarea {
|
||||
flex-shrink: 1;
|
||||
width: 30rem;
|
||||
height: 6rem;
|
||||
}
|
||||
Reference in New Issue
Block a user