Rewrite some components to use render method

This commit is contained in:
eraden 2021-04-18 15:37:45 +02:00
parent 682066b561
commit 2d7ce5762a
4 changed files with 304 additions and 295 deletions

View File

@ -48,8 +48,8 @@ impl<'l> Default for ChildBuilder<'l> {
} }
} }
impl<'l> ToNode for ChildBuilder<'l> { impl<'l> ChildBuilder<'l> {
fn into_node(self) -> Node<Msg> { pub fn render(self) -> Node<Msg> {
let ChildBuilder { let ChildBuilder {
field_id, field_id,
name, name,
@ -60,10 +60,10 @@ impl<'l> ToNode for ChildBuilder<'l> {
} = self; } = self;
let id = field_id.to_string(); let id = field_id.to_string();
let field_id_clone = field_id.clone(); let handler: EventHandler<Msg> = {
let handler: EventHandler<Msg> = mouse_ev(Ev::Click, move |_| { let id = field_id.clone();
Msg::U32InputChanged(field_id_clone, value) mouse_ev(Ev::Click, move |_| Msg::U32InputChanged(id, value))
}); };
div![ div![
C![ C![
@ -81,7 +81,7 @@ impl<'l> ToNode for ChildBuilder<'l> {
} }
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct StyledCheckbox<'l, Options> pub struct StyledCheckbox<'l, Options>
where where
Options: Iterator<Item = ChildBuilder<'l>>, Options: Iterator<Item = ChildBuilder<'l>>,
@ -90,15 +90,23 @@ where
pub class_list: &'l str, pub class_list: &'l str,
} }
impl<'l, Options> Default for StyledCheckbox<'l, Options> impl<'l, Options> StyledCheckbox<'l, Options>
where where
Options: Iterator<Item = ChildBuilder<'l>>, Options: Iterator<Item = ChildBuilder<'l>>,
{ {
fn default() -> Self { pub fn render(self) -> Node<Msg> {
Self { let StyledCheckbox {
options: None, options,
class_list: "", class_list,
} } = self;
div![
C!["styledCheckbox", class_list],
options.map_or_else(
|| vec![Node::Empty],
|v| v.map(ChildBuilder::render).collect(),
)
]
} }
} }
@ -107,23 +115,6 @@ where
Options: Iterator<Item = ChildBuilder<'l>>, Options: Iterator<Item = ChildBuilder<'l>>,
{ {
fn into_node(self) -> Node<Msg> { fn into_node(self) -> Node<Msg> {
render(self) self.render()
} }
} }
fn render<'l, Options>(values: StyledCheckbox<'l, Options>) -> Node<Msg>
where
Options: Iterator<Item = ChildBuilder<'l>>,
{
let StyledCheckbox {
options,
class_list,
} = values;
let opt: Vec<Node<Msg>> = match options {
Some(options) => options.map(|child| child.into_node()).collect(),
_ => vec![Node::Empty],
};
div![C!["styledCheckbox", class_list], opt,]
}

View File

@ -20,6 +20,53 @@ pub struct StyledConfirmModal<'l> {
pub on_confirm: Option<EventHandler<Msg>>, pub on_confirm: Option<EventHandler<Msg>>,
} }
impl<'l> StyledConfirmModal<'l> {
pub fn render(self) -> Node<Msg> {
let StyledConfirmModal {
title,
message,
confirm_text,
cancel_text,
on_confirm,
} = self;
let title = if title.is_empty() { TITLE } else { title };
let message = if message.is_empty() { MESSAGE } else { message };
let confirm_button = StyledButton {
text: Some(match confirm_text {
s if s.is_empty() => CONFIRM_TEXT,
_ => confirm_text,
}),
on_click: on_confirm,
..Default::default()
}
.render();
let cancel_button = StyledButton {
text: Some(match cancel_text {
s if s.is_empty() => CANCEL_TEXT,
_ => cancel_text,
}),
variant: ButtonVariant::Secondary,
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ModalDropped)),
..Default::default()
}
.render();
StyledModal {
width: Some(600),
children: vec![
div![C!["title"], title],
p![C!["message"], message],
div![C!["actions"], confirm_button, cancel_button],
],
class_list: "confirmModal",
..Default::default()
}
.into_node()
}
}
impl<'l> Default for StyledConfirmModal<'l> { impl<'l> Default for StyledConfirmModal<'l> {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -34,60 +81,6 @@ impl<'l> Default for StyledConfirmModal<'l> {
impl<'l> ToNode for StyledConfirmModal<'l> { impl<'l> ToNode for StyledConfirmModal<'l> {
fn into_node(self) -> Node<Msg> { fn into_node(self) -> Node<Msg> {
render(self) self.render()
} }
} }
pub fn render(values: StyledConfirmModal) -> Node<Msg> {
let StyledConfirmModal {
title,
message,
confirm_text,
cancel_text,
on_confirm,
} = values;
let title = if title.is_empty() { TITLE } else { title };
let message = if message.is_empty() { MESSAGE } else { message };
let confirm_text = if confirm_text.is_empty() {
CONFIRM_TEXT
} else {
confirm_text
};
let cancel_text = if cancel_text.is_empty() {
CANCEL_TEXT
} else {
cancel_text
};
let message_node = match message {
_ if message.is_empty() => empty![],
_ => p![C!["message"], message],
};
let confirm_button = StyledButton {
text: Some(confirm_text),
on_click: on_confirm,
..Default::default()
}
.into_node();
let cancel_button = StyledButton {
text: Some(cancel_text),
variant: ButtonVariant::Secondary,
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ModalDropped)),
..Default::default()
}
.into_node();
StyledModal {
width: Some(600),
children: vec![
div![C!["title"], title],
message_node,
div![C!["actions"], confirm_button, cancel_button],
],
class_list: "confirmModal",
..Default::default()
}
.into_node()
}

View File

@ -75,208 +75,231 @@ pub struct StyledDateTimeInput {
pub popup_visible: bool, pub popup_visible: bool,
} }
impl StyledDateTimeInput {
pub fn render(self) -> Node<Msg> {
let timestamp = self
.timestamp
.unwrap_or_else(|| chrono::Utc::now().naive_utc());
let start = timestamp.with_day0(0).unwrap();
let end = (start + Duration::days(32)).with_day0(0).unwrap();
let calendar_start = StyledDateTimeInput::calendar_start(start.clone());
let calendar_end = StyledDateTimeInput::calendar_end(end.clone());
let current_month_range = start..=end;
let mut current = calendar_start;
let mut weeks = vec![];
let range = calendar_start..=calendar_end;
let mut current_week = vec![];
loop {
if !range.contains(&current) {
break;
}
if current.weekday() == Weekday::Mon && !current_week.is_empty() {
weeks.push(div![C!["week"], current_week]);
current_week = vec![];
}
current_week.push(
DayCell {
field_id: &self.field_id,
timestamp: &timestamp,
current: &current,
current_month_range: &current_month_range,
}
.render(),
);
current += Duration::days(1);
}
if !current_week.is_empty() {
weeks.push(div![C!["week"], current_week]);
}
let left_action = {
let field_id = self.field_id.clone();
let current = timestamp;
let on_click_left = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
let last_day_of_prev_month = current.with_day0(0).unwrap() - Duration::days(1);
let date = last_day_of_prev_month
.with_day0(timestamp.day0())
.unwrap_or(last_day_of_prev_month);
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::MonthChanged(Some(date)),
)
});
StyledButton {
on_click: Some(on_click_left),
icon: Some(Icon::DoubleLeft.into_node()),
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node()
};
let right_action = {
let field_id = self.field_id.clone();
let current = timestamp;
let on_click_right = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
let first_day_of_next_month = (current + Duration::days(32)).with_day0(0).unwrap();
let last_day_of_next_month = (first_day_of_next_month + Duration::days(32))
.with_day0(0)
.unwrap()
- Duration::days(1);
let date = first_day_of_next_month
.with_day0(timestamp.day0())
.unwrap_or(last_day_of_next_month);
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::MonthChanged(Some(date)),
)
});
StyledButton {
on_click: Some(on_click_right),
icon: Some(Icon::DoubleRight.into_node()),
variant: ButtonVariant::Empty,
..Default::default()
}
.render()
};
let tooltip = StyledTooltip {
visible: self.popup_visible,
class_list: "",
children: vec![
h2![
left_action,
span![current.format("%B %Y").to_string()],
right_action
],
div![
C!["calendar"],
div![
C!["weekHeader week"],
div![C!["day"], format!("{}", Weekday::Mon).as_str()],
div![C!["day"], format!("{}", Weekday::Tue).as_str()],
div![C!["day"], format!("{}", Weekday::Wed).as_str()],
div![C!["day"], format!("{}", Weekday::Thu).as_str()],
div![C!["day"], format!("{}", Weekday::Fri).as_str()],
div![C!["day"], format!("{}", Weekday::Sat).as_str()],
div![C!["day"], format!("{}", Weekday::Sun).as_str()],
],
weeks
],
],
variant: TooltipVariant::DateTimeBuilder,
}
.render();
let input = {
let field_id = self.field_id.clone();
let visible = self.popup_visible;
let on_focus = ev(Ev::Click, move |ev| {
ev.prevent_default();
ev.stop_propagation();
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::PopupVisibilityChanged(!visible),
)
});
let text = self
.timestamp
.unwrap_or_else(|| Utc::now().naive_utc())
.date()
.format("%d/%m/%Y")
.to_string();
StyledButton {
on_click: Some(on_focus),
text: Some(text.as_str()),
variant: ButtonVariant::Empty,
..Default::default()
}
.render()
};
div![
C!["styledDateTimeInput", format!("{}", self.field_id)],
input,
tooltip,
]
}
#[inline(always)]
fn calendar_start(start: NaiveDateTime) -> NaiveDateTime {
match start.weekday() {
Weekday::Mon => start,
Weekday::Tue => start - Duration::days(1),
Weekday::Wed => start - Duration::days(2),
Weekday::Thu => start - Duration::days(3),
Weekday::Fri => start - Duration::days(4),
Weekday::Sat => start - Duration::days(5),
Weekday::Sun => start - Duration::days(6),
}
}
#[inline(always)]
fn calendar_end(end: NaiveDateTime) -> NaiveDateTime {
match end.weekday() {
Weekday::Mon => end + Duration::days(6),
Weekday::Tue => end + Duration::days(5),
Weekday::Wed => end + Duration::days(4),
Weekday::Thu => end + Duration::days(3),
Weekday::Fri => end + Duration::days(2),
Weekday::Sat => end + Duration::days(1),
Weekday::Sun => end,
}
}
}
impl ToNode for StyledDateTimeInput { impl ToNode for StyledDateTimeInput {
fn into_node(self) -> Node<Msg> { fn into_node(self) -> Node<Msg> {
render(self) self.render()
} }
} }
fn render(values: StyledDateTimeInput) -> Node<Msg> { pub struct DayCell<'l> {
let timestamp = values field_id: &'l FieldId,
.timestamp timestamp: &'l NaiveDateTime,
.unwrap_or_else(|| chrono::Utc::now().naive_utc()); current: &'l NaiveDateTime,
let start = timestamp.with_day0(0).unwrap(); current_month_range: &'l RangeInclusive<NaiveDateTime>,
let end = (start + Duration::days(32)).with_day0(0).unwrap(); }
let calendar_start = match start.weekday() { impl<'l> DayCell<'l> {
Weekday::Mon => start, pub fn render(self) -> Node<Msg> {
Weekday::Tue => start - Duration::days(1), let on_click = {
Weekday::Wed => start - Duration::days(2), let field_id = self.field_id.clone();
Weekday::Thu => start - Duration::days(3), let date = *self.current;
Weekday::Fri => start - Duration::days(4), ev(Ev::Click, move |ev| {
Weekday::Sat => start - Duration::days(5), ev.stop_propagation();
Weekday::Sun => start - Duration::days(6), ev.prevent_default();
}; Msg::StyledDateTimeInputChanged(
let calendar_end = match end.weekday() { field_id,
Weekday::Mon => end + Duration::days(6), StyledDateTimeChanged::DayChanged(Some(date)),
Weekday::Tue => end + Duration::days(5), )
Weekday::Wed => end + Duration::days(4), })
Weekday::Thu => end + Duration::days(3), };
Weekday::Fri => end + Duration::days(2), div![
Weekday::Sat => end + Duration::days(1), C![
Weekday::Sun => end, "day",
}; format!("{}", self.current.weekday()),
let current_month_range = start..=end; IF![self.is_selected() => "selected"],
let mut current = calendar_start; if self.current_month_range.contains(&self.current) {
let mut weeks = vec![]; "inCurrentMonth"
let range = calendar_start..=calendar_end; } else {
let mut current_week = vec![]; "outCurrentMonth"
loop { },
if !range.contains(&current) {
break;
}
if current.weekday() == Weekday::Mon && !current_week.is_empty() {
weeks.push(div![C!["week"], current_week]);
current_week = vec![];
}
current_week.push(day_cell(
&values.field_id,
&timestamp,
&current,
&current_month_range,
));
current += Duration::days(1);
}
if !current_week.is_empty() {
weeks.push(div![C!["week"], current_week]);
}
let left_action = {
let field_id = values.field_id.clone();
let current = timestamp;
let on_click_left = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
let last_day_of_prev_month = current.with_day0(0).unwrap() - Duration::days(1);
let date = last_day_of_prev_month
.with_day0(timestamp.day0())
.unwrap_or(last_day_of_prev_month);
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::MonthChanged(Some(date)),
)
});
StyledButton {
on_click: Some(on_click_left),
icon: Some(Icon::DoubleLeft.into_node()),
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node()
};
let right_action = {
let field_id = values.field_id.clone();
let current = timestamp;
let on_click_right = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
let first_day_of_next_month = (current + Duration::days(32)).with_day0(0).unwrap();
let last_day_of_next_month = (first_day_of_next_month + Duration::days(32))
.with_day0(0)
.unwrap()
- Duration::days(1);
let date = first_day_of_next_month
.with_day0(timestamp.day0())
.unwrap_or(last_day_of_next_month);
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::MonthChanged(Some(date)),
)
});
StyledButton {
on_click: Some(on_click_right),
icon: Some(Icon::DoubleRight.into_node()),
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node()
};
let header_text = current.format("%B %Y").to_string();
let tooltip = StyledTooltip {
visible: values.popup_visible,
class_list: "",
children: vec![
h2![left_action, span![header_text], right_action],
div![
C!["calendar"],
div![
C!["weekHeader week"],
div![C!["day"], format!("{}", Weekday::Mon).as_str()],
div![C!["day"], format!("{}", Weekday::Tue).as_str()],
div![C!["day"], format!("{}", Weekday::Wed).as_str()],
div![C!["day"], format!("{}", Weekday::Thu).as_str()],
div![C!["day"], format!("{}", Weekday::Fri).as_str()],
div![C!["day"], format!("{}", Weekday::Sat).as_str()],
div![C!["day"], format!("{}", Weekday::Sun).as_str()],
],
weeks
], ],
], format!("{}", self.current.day()).as_str(),
variant: TooltipVariant::DateTimeBuilder, on_click,
]
} }
.into_node();
let input = { fn is_selected(&self) -> bool {
let field_id = values.field_id.clone(); *self.timestamp == *self.current
let visible = values.popup_visible; }
let on_focus = ev(Ev::Click, move |ev| {
ev.prevent_default();
ev.stop_propagation();
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::PopupVisibilityChanged(!visible),
)
});
let text = values
.timestamp
.unwrap_or_else(|| Utc::now().naive_utc())
.date()
.format("%d/%m/%Y")
.to_string();
StyledButton {
on_click: Some(on_focus),
text: Some(text.as_str()),
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node()
};
div![
C!["styledDateTimeInput"],
attrs![At::Class => format!("{}", values.field_id).as_str()],
input,
tooltip,
]
}
fn day_cell(
field_id: &FieldId,
timestamp: &NaiveDateTime,
current: &NaiveDateTime,
current_month_range: &RangeInclusive<NaiveDateTime>,
) -> Node<Msg> {
let selected_day_class = if *timestamp == *current {
"selected"
} else {
""
};
let on_click = {
let field_id = field_id.clone();
let date = *current;
ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
Msg::StyledDateTimeInputChanged(field_id, StyledDateTimeChanged::DayChanged(Some(date)))
})
};
div![
C!["day"],
attrs![At::Class => format!("{}", current.weekday())],
if current_month_range.contains(&current) {
C!["inCurrentMonth"]
} else {
C!["outCurrentMonth"]
},
C![selected_day_class],
format!("{}", current.day()).as_str(),
on_click,
]
} }

View File

@ -44,22 +44,24 @@ pub struct StyledTooltip<'l> {
pub variant: TooltipVariant, pub variant: TooltipVariant,
} }
impl<'l> ToNode for StyledTooltip<'l> { impl<'l> StyledTooltip<'l> {
fn into_node(self) -> Node<Msg> { pub fn render(self) -> Node<Msg> {
render(self) let StyledTooltip {
visible,
class_list,
children,
variant,
} = self;
if visible {
div![C!["styledTooltip", class_list, variant.to_str()], children]
} else {
empty!()
}
} }
} }
pub fn render(values: StyledTooltip) -> Node<Msg> { impl<'l> ToNode for StyledTooltip<'l> {
let StyledTooltip { fn into_node(self) -> Node<Msg> {
visible, self.render()
class_list,
children,
variant,
} = values;
if visible {
div![C!["styledTooltip", class_list, variant.to_str()], children]
} else {
empty!()
} }
} }