Upgrade yew

This commit is contained in:
Thomas Gideon 2021-09-06 17:17:51 -04:00
parent 0ff7d59f63
commit dd6df30370
18 changed files with 129 additions and 71 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bootstrap-rs" name = "bootstrap-rs"
version = "0.4.0" version = "0.5.0"
authors = ["Thomas Gideon <cmdln@thecommandline.net>"] authors = ["Thomas Gideon <cmdln@thecommandline.net>"]
edition = "2018" edition = "2018"
@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
validate = [ "validator" ] validate = [ "validator" ]
[dependencies] [dependencies]
yew = "~0.17.2" yew = "^0.18.0"
log = "~0.4.8" log = "~0.4.8"
serde_json = "^1.0.40" serde_json = "^1.0.40"
serde = "^1.0.98" serde = "^1.0.98"

View File

@ -17,10 +17,10 @@ pub enum Request {
Clear, Clear,
} }
impl Into<Option<(Color, String)>> for &Request { impl From<&Request> for Option<(Color, String)> {
fn into(self) -> Option<(Color, String)> { fn from(request: &Request) -> Option<(Color, String)> {
use Request::*; use Request::*;
match self { match request {
Primary(alert) => Some((Color::Primary, alert.clone())), Primary(alert) => Some((Color::Primary, alert.clone())),
Secondary(alert) => Some((Color::Secondary, alert.clone())), Secondary(alert) => Some((Color::Secondary, alert.clone())),
Success(alert) => Some((Color::Success, alert.clone())), Success(alert) => Some((Color::Success, alert.clone())),

View File

@ -11,6 +11,8 @@ use yew::prelude::*;
pub struct Alert { pub struct Alert {
link: ComponentLink<Self>, link: ComponentLink<Self>,
props: Props, props: Props,
prefix: String,
prefixed_color: String,
} }
impl Component for Alert { impl Component for Alert {
@ -18,7 +20,12 @@ impl Component for Alert {
type Message = (); type Message = ();
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { link, props } Self {
link,
props,
prefix: String::from("alert"),
prefixed_color: String::default(),
}
} }
fn update(&mut self, _: Self::Message) -> ShouldRender { fn update(&mut self, _: Self::Message) -> ShouldRender {
@ -27,6 +34,9 @@ impl Component for Alert {
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn change(&mut self, props: Self::Properties) -> ShouldRender {
if props.color != self.props.color {
self.prefixed_color = props.color.with_prefix(&self.prefix);
}
render_if_ne(&mut self.props, props) render_if_ne(&mut self.props, props)
} }
@ -45,10 +55,10 @@ impl Component for Alert {
</button> </button>
</div> </div>
}; };
render::render_with_prefix( render::render_with_prefix(&self.props, vec![&self.prefix, &self.prefixed_color], html)
&self.props,
vec!["alert", &self.props.color.with_prefix("alert")],
html,
)
} }
fn rendered(&mut self, _first_render: bool) {}
fn destroy(&mut self) {}
} }

View File

@ -133,6 +133,6 @@ mod tests {
> >
</li> </li>
}; };
assert_eq!(expected, item.view()); crate::test::assert_attrs_eq(expected, item.view());
} }
} }

View File

@ -5,8 +5,11 @@ mod toolbar;
use self::props::Props; use self::props::Props;
pub use self::{group::ButtonGroup, toolbar::ButtonToolbar}; pub use self::{group::ButtonGroup, toolbar::ButtonToolbar};
use crate::{prelude::*, render}; use crate::{prelude::*, render};
use std::fmt::{Display, Formatter, Result}; use std::{
use yew::prelude::*; borrow::Cow,
fmt::{Display, Formatter, Result},
};
use yew::{html::IntoOptPropValue, prelude::*};
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum ButtonType { pub enum ButtonType {
@ -15,6 +18,27 @@ pub enum ButtonType {
Reset, Reset,
} }
impl IntoOptPropValue<Cow<'static, str>> for ButtonType {
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
match self {
Self::Button => Some(Cow::from("button")),
Self::Submit => Some(Cow::from("submit")),
Self::Reset => Some(Cow::from("reset")),
}
}
}
impl IntoOptPropValue<Cow<'static, str>> for &ButtonType {
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
use ButtonType::*;
match self {
Button => Some(Cow::from("button")),
Submit => Some(Cow::from("submit")),
Reset => Some(Cow::from("reset")),
}
}
}
impl Default for ButtonType { impl Default for ButtonType {
fn default() -> Self { fn default() -> Self {
ButtonType::Button ButtonType::Button
@ -57,7 +81,7 @@ impl Component for Button {
fn view(&self) -> Html { fn view(&self) -> Html {
let html = html! { let html = html! {
<button <button
button=self.props.button_type button=&self.props.button_type
onclick=self.link.callback(|_| ()) onclick=self.link.callback(|_| ())
disabled=self.props.disabled.unwrap_or_default() disabled=self.props.disabled.unwrap_or_default()
> >

View File

@ -44,6 +44,6 @@ mod test {
</div> </div>
}; };
assert_eq!(expected, comp.view()); crate::test::assert_attrs_eq(expected, comp.view());
} }
} }

View File

@ -41,6 +41,6 @@ mod test {
</p> </p>
}; };
assert_eq!(expected, comp.view()); crate::test::assert_attrs_eq(expected, comp.view());
} }
} }

View File

@ -43,6 +43,6 @@ mod tests {
<div class="container m-3 pt-3"> <div class="container m-3 pt-3">
</div> </div>
}; };
assert_eq!(expected, container.view()); crate::test::assert_attrs_eq(expected, container.view());
} }
} }

View File

@ -6,9 +6,14 @@ use self::props::Props;
pub use self::{group::InputGroup, textarea::TextArea}; pub use self::{group::InputGroup, textarea::TextArea};
use crate::{prelude::*, render}; use crate::{prelude::*, render};
use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fmt::{Display, Formatter, Result as FmtResult};
use std::{
borrow::Cow,
fmt::{Display, Formatter, Result as FmtResult},
};
#[cfg(feature = "validate")] #[cfg(feature = "validate")]
use validator::ValidationErrors; use validator::ValidationErrors;
use yew::prelude::*; use yew::prelude::*;
use yew::{html::IntoOptPropValue, prelude::*};
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum InputType { pub enum InputType {
@ -19,6 +24,18 @@ pub enum InputType {
Color, Color,
} }
impl IntoOptPropValue<Cow<'static, str>> for &InputType {
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
match self {
InputType::Text => Some(Cow::from("text")),
InputType::Date => Some(Cow::from("date")),
InputType::DateTime => Some(Cow::from("datetime-local")),
InputType::Checkbox => Some(Cow::from("checkbox")),
InputType::Color => Some(Cow::from("color")),
}
}
}
impl Display for InputType { impl Display for InputType {
fn fmt(&self, f: &mut Formatter) -> FmtResult { fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self { match self {
@ -78,9 +95,9 @@ impl Component for Input {
let html = html! { let html = html! {
<input <input
type=input_type type=input_type
value=&self.state value=self.state.clone()
readonly=self.props.readonly readonly=self.props.readonly
onchange=self.link.callback(|evt| InputChange(evt)) onchange=self.link.callback(InputChange)
/> />
}; };
render::render_with_prefix(&self.props, prefix, html) render::render_with_prefix(&self.props, prefix, html)

View File

@ -40,16 +40,14 @@ impl Component for TextArea {
#[cfg(not(feature = "validate"))] #[cfg(not(feature = "validate"))]
fn view(&self) -> Html { fn view(&self) -> Html {
let prefix = vec!["form-control", &valid_as_class(&self.props.valid)]; let prefix = vec!["form-control", valid_as_class(&self.props.valid)];
let html = { let html = html! {
html! {
<textarea <textarea
readonly=self.props.readonly readonly=self.props.readonly
onchange=self.link.callback(|evt| InputChange(evt)) onchange=self.link.callback(InputChange)
> >
{ &self.state } { &self.state }
</textarea> </textarea>
}
}; };
render::render_with_prefix(&self.props, prefix, html) render::render_with_prefix(&self.props, prefix, html)
} }

View File

@ -20,3 +20,18 @@ pub use self::{
input::{Input, InputGroup, TextArea}, input::{Input, InputGroup, TextArea},
jumbotron::Jumbotron, jumbotron::Jumbotron,
}; };
#[cfg(test)]
pub(crate) mod test {
use yew::virtual_dom::VNode;
pub(crate) fn assert_attrs_eq(expected: VNode, comp: VNode) {
match (expected, comp) {
(VNode::VTag(mut expected), VNode::VTag(mut comp)) => assert_eq!(
expected.attributes.get_mut_index_map(),
comp.attributes.get_mut_index_map()
),
_ => panic!("One or both components were not tags!"),
}
}
}

View File

@ -1,15 +1,15 @@
use super::{Color, Edge}; use super::{Color, Edge};
use crate::props::IntoBsClass; use yew::Classes;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct Border(pub Edge, pub Color); pub struct Border(pub Edge, pub Color);
impl IntoBsClass for Border { impl From<&&Border> for Classes {
fn as_classname(&self) -> String { fn from(border: &&Border) -> Classes {
let edge = match self.0 { let edge = match border.0 {
Edge::All => "border".to_owned(), Edge::All => "border".to_owned(),
_ => self.0.with_prefix("border"), _ => border.0.with_prefix("border"),
}; };
format!("{} {}", edge, self.1.with_prefix("border")) Classes::from(format!("{} {}", edge, border.1.with_prefix("border")))
} }
} }

View File

@ -1,3 +1,5 @@
use yew::html::ImplicitClone;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum Color { pub enum Color {
Primary, Primary,
@ -12,6 +14,8 @@ pub enum Color {
Unset, Unset,
} }
impl ImplicitClone for Color {}
impl Default for Color { impl Default for Color {
fn default() -> Self { fn default() -> Self {
Self::Unset Self::Unset

View File

@ -1,10 +1,10 @@
use crate::props::IntoBsClass; use yew::Classes;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Margin(pub super::Edge, pub usize); pub struct Margin(pub super::Edge, pub usize);
impl IntoBsClass for Margin { impl From<&&Margin> for Classes {
fn as_classname(&self) -> String { fn from(margin: &&Margin) -> Classes {
format!("m{}-{}", self.0, self.1) Classes::from(format!("m{}-{}", margin.0, margin.1))
} }
} }

View File

@ -15,9 +15,9 @@ use yew::prelude::*;
/// edit or correct as needed. /// edit or correct as needed.
pub struct InputString<S: AsRef<str>>(pub S); pub struct InputString<S: AsRef<str>>(pub S);
impl<S: AsRef<str>> Into<Option<String>> for InputString<S> { impl<S: AsRef<str>> From<InputString<S>> for Option<String> {
fn into(self) -> Option<String> { fn from(input: InputString<S>) -> Option<String> {
let InputString(input) = self; let InputString(input) = input;
if input.as_ref().is_empty() { if input.as_ref().is_empty() {
None None
} else { } else {

View File

@ -1,10 +1,10 @@
use crate::props::IntoBsClass; use yew::Classes;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Padding(pub super::Edge, pub usize); pub struct Padding(pub super::Edge, pub usize);
impl IntoBsClass for Padding { impl From<&&Padding> for Classes {
fn as_classname(&self) -> String { fn from(padding: &&Padding) -> Classes {
format!("p{}-{}", self.0, self.1) Classes::from(format!("p{}-{}", padding.0, padding.1))
} }
} }

View File

@ -4,10 +4,6 @@ use std::collections::HashMap;
use validator::ValidationErrors; use validator::ValidationErrors;
use yew::{prelude::*, virtual_dom::VNode}; use yew::{prelude::*, virtual_dom::VNode};
pub(crate) trait IntoBsClass {
fn as_classname(&self) -> String;
}
pub(crate) fn collect_props<'a, T>(t: &'a Option<T>, ts: &'a [T]) -> Vec<&'a T> { pub(crate) fn collect_props<'a, T>(t: &'a Option<T>, ts: &'a [T]) -> Vec<&'a T> {
if let Some(t) = t.as_ref() { if let Some(t) = t.as_ref() {
let mut r = vec![t]; let mut r = vec![t];
@ -89,12 +85,12 @@ pub struct BootstrapProps<'a> {
impl<'a> BootstrapProps<'a> { impl<'a> BootstrapProps<'a> {
pub fn add_attributes(&self, html: &mut VNode) { pub fn add_attributes(&self, html: &mut VNode) {
if let VNode::VTag(tag) = html { if let VNode::VTag(tag) = html {
let attrs = self for key in self.attributes.keys() {
.attributes if let Some(value) = &self.attributes.get(key) {
.iter() let value = **value;
.map(|(key, value)| (key.to_string(), (*value).to_owned())) tag.add_attribute(key, value.clone());
.collect(); }
tag.add_attributes(attrs); }
} }
} }
@ -106,21 +102,15 @@ impl<'a> BootstrapProps<'a> {
paddings, paddings,
.. ..
} = self; } = self;
let classes = prefix.into(); let mut classes: Classes = prefix.into();
let classes = classes.extend((*class).to_owned()); classes.push((*class).to_owned());
let classes = classes.extend(into_classnames(borders)); classes.extend(borders);
let classes = classes.extend(into_classnames(margins)); classes.extend(margins);
classes.extend(into_classnames(paddings)) classes.extend(paddings);
classes
} }
} }
fn into_classnames<C: IntoBsClass>(c: &[&C]) -> Classes {
c.iter().fold(Classes::new(), |mut cs, c| {
cs.push(&c.as_classname());
cs
})
}
pub(crate) fn add_opt_attr<'a>( pub(crate) fn add_opt_attr<'a>(
attrs: &mut HashMap<&str, &'a String>, attrs: &mut HashMap<&str, &'a String>,
name: &'static str, name: &'static str,
@ -146,9 +136,9 @@ mod test {
<div class="" aria-label="override" /> <div class="" aria-label="override" />
}; };
assert_eq!( crate::test::assert_attrs_eq(
expected, expected,
crate::render::render_with_prefix(&props, "", html! { <div aria-label="test" /> }) crate::render::render_with_prefix(&props, "", html! { <div aria-label="test" /> }),
); );
} }
@ -163,9 +153,9 @@ mod test {
<div class="" aria-label="some-label" /> <div class="" aria-label="some-label" />
}; };
assert_eq!( crate::test::assert_attrs_eq(
expected, expected,
crate::render::render_with_prefix(&props, "", html! { <div /> }) crate::render::render_with_prefix(&props, "", html! { <div /> }),
); );
} }
} }

View File

@ -7,13 +7,13 @@ pub(crate) fn render_with_prefix<'a, B: Into<BootstrapProps<'a>>, C: Into<Classe
mut node: VNode, mut node: VNode,
) -> VNode { ) -> VNode {
let props = props.into(); let props = props.into();
props.add_attributes(&mut node);
if let VNode::VTag(tag) = &mut node { if let VNode::VTag(tag) = &mut node {
let classes = &props.calculate_classes(prefix); let classes = &props.calculate_classes(prefix);
if !classes.is_empty() { if !classes.is_empty() {
tag.add_attribute("class", classes); tag.add_attribute("class", classes.to_string());
} }
} }
props.add_attributes(&mut node);
node node
} }
@ -49,6 +49,6 @@ mod tests {
<div class="first second m-3"/> <div class="first second m-3"/>
}; };
assert_eq!(expected, comp); crate::test::assert_attrs_eq(expected, comp);
} }
} }