Note: This PR doesn't handle errors around Jpeg files very well. Co-authored-by: Bojidar Marinov <bojidar.marinov.bg@gmail.com> Co-authored-by: Austin Alvarado <pixelrazor@gmail.com>
191 lines
5.6 KiB
Rust
191 lines
5.6 KiB
Rust
use crate::{
|
|
components::form::{date_input::DateTimeInput, file_input::JpegFileInput},
|
|
infra::{schema::AttributeType, tooltip::Tooltip},
|
|
};
|
|
use web_sys::Element;
|
|
use yew::{
|
|
function_component, html, use_effect_with_deps, use_node_ref, virtual_dom::AttrValue,
|
|
Component, Context, Html, Properties,
|
|
};
|
|
|
|
#[derive(Properties, PartialEq)]
|
|
struct AttributeInputProps {
|
|
name: AttrValue,
|
|
attribute_type: AttributeType,
|
|
#[prop_or(None)]
|
|
value: Option<String>,
|
|
}
|
|
|
|
#[function_component(AttributeInput)]
|
|
fn attribute_input(props: &AttributeInputProps) -> Html {
|
|
let input_type = match props.attribute_type {
|
|
AttributeType::String => "text",
|
|
AttributeType::Integer => "number",
|
|
AttributeType::DateTime => {
|
|
return html! {
|
|
<DateTimeInput name={props.name.clone()} value={props.value.clone()} />
|
|
}
|
|
}
|
|
AttributeType::Jpeg => {
|
|
return html! {
|
|
<JpegFileInput name={props.name.clone()} value={props.value.clone()} />
|
|
}
|
|
}
|
|
};
|
|
|
|
html! {
|
|
<input
|
|
type={input_type}
|
|
name={props.name.clone()}
|
|
class="form-control"
|
|
value={props.value.clone()} />
|
|
}
|
|
}
|
|
|
|
#[derive(Properties, PartialEq)]
|
|
struct AttributeLabelProps {
|
|
pub name: String,
|
|
}
|
|
#[function_component(AttributeLabel)]
|
|
fn attribute_label(props: &AttributeLabelProps) -> Html {
|
|
let tooltip_ref = use_node_ref();
|
|
|
|
use_effect_with_deps(
|
|
move |tooltip_ref| {
|
|
Tooltip::new(
|
|
tooltip_ref
|
|
.cast::<Element>()
|
|
.expect("Tooltip element should exist"),
|
|
);
|
|
|| {}
|
|
},
|
|
tooltip_ref.clone(),
|
|
);
|
|
|
|
html! {
|
|
<label for={props.name.clone()}
|
|
class="form-label col-4 col-form-label"
|
|
>
|
|
{props.name[0..1].to_uppercase() + &props.name[1..].replace('_', " ")}{":"}
|
|
<button
|
|
class="btn btn-sm btn-link"
|
|
type="button"
|
|
data-bs-placement="right"
|
|
title={props.name.clone()}
|
|
ref={tooltip_ref}>
|
|
<i class="bi bi-info-circle" aria-label="Info" />
|
|
</button>
|
|
</label>
|
|
}
|
|
}
|
|
|
|
#[derive(Properties, PartialEq)]
|
|
pub struct SingleAttributeInputProps {
|
|
pub name: String,
|
|
pub attribute_type: AttributeType,
|
|
#[prop_or(None)]
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
#[function_component(SingleAttributeInput)]
|
|
pub fn single_attribute_input(props: &SingleAttributeInputProps) -> Html {
|
|
html! {
|
|
<div class="row mb-3">
|
|
<AttributeLabel name={props.name.clone()} />
|
|
<div class="col-8">
|
|
<AttributeInput
|
|
attribute_type={props.attribute_type.clone()}
|
|
name={props.name.clone()}
|
|
value={props.value.clone()} />
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
#[derive(Properties, PartialEq)]
|
|
pub struct ListAttributeInputProps {
|
|
pub name: String,
|
|
pub attribute_type: AttributeType,
|
|
#[prop_or(vec!())]
|
|
pub values: Vec<String>,
|
|
}
|
|
|
|
pub enum ListAttributeInputMsg {
|
|
Remove(usize),
|
|
Append,
|
|
}
|
|
|
|
pub struct ListAttributeInput {
|
|
indices: Vec<usize>,
|
|
next_index: usize,
|
|
values: Vec<String>,
|
|
}
|
|
impl Component for ListAttributeInput {
|
|
type Message = ListAttributeInputMsg;
|
|
type Properties = ListAttributeInputProps;
|
|
|
|
fn create(ctx: &Context<Self>) -> Self {
|
|
let values = ctx.props().values.clone();
|
|
Self {
|
|
indices: (0..values.len()).collect(),
|
|
next_index: values.len(),
|
|
values,
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
match msg {
|
|
ListAttributeInputMsg::Remove(removed) => {
|
|
self.indices.retain_mut(|x| *x != removed);
|
|
}
|
|
ListAttributeInputMsg::Append => {
|
|
self.indices.push(self.next_index);
|
|
self.next_index += 1;
|
|
}
|
|
};
|
|
true
|
|
}
|
|
|
|
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
|
if ctx.props().values != self.values {
|
|
self.values.clone_from(&ctx.props().values);
|
|
self.indices = (0..self.values.len()).collect();
|
|
self.next_index = self.values.len();
|
|
}
|
|
true
|
|
}
|
|
|
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
let props = &ctx.props();
|
|
let link = &ctx.link();
|
|
html! {
|
|
<div class="row mb-3">
|
|
<AttributeLabel name={props.name.clone()} />
|
|
<div class="col-8">
|
|
{self.indices.iter().map(|&i| html! {
|
|
<div class="input-group mb-2" key={i}>
|
|
<AttributeInput
|
|
attribute_type={props.attribute_type.clone()}
|
|
name={props.name.clone()}
|
|
value={props.values.get(i).cloned().unwrap_or_default()} />
|
|
<button
|
|
class="btn btn-danger"
|
|
type="button"
|
|
onclick={link.callback(move |_| ListAttributeInputMsg::Remove(i))}>
|
|
<i class="bi-x-circle-fill" aria-label="Remove value" />
|
|
</button>
|
|
</div>
|
|
}).collect::<Html>()}
|
|
<button
|
|
class="btn btn-secondary"
|
|
type="button"
|
|
onclick={link.callback(|_| ListAttributeInputMsg::Append)}>
|
|
<i class="bi-plus-circle me-2"></i>
|
|
{"Add value"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
}
|