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> {
fn into_node(self) -> Node<Msg> {
impl<'l> ChildBuilder<'l> {
pub fn render(self) -> Node<Msg> {
let ChildBuilder {
field_id,
name,
@ -60,10 +60,10 @@ impl<'l> ToNode for ChildBuilder<'l> {
} = self;
let id = field_id.to_string();
let field_id_clone = field_id.clone();
let handler: EventHandler<Msg> = mouse_ev(Ev::Click, move |_| {
Msg::U32InputChanged(field_id_clone, value)
});
let handler: EventHandler<Msg> = {
let id = field_id.clone();
mouse_ev(Ev::Click, move |_| Msg::U32InputChanged(id, value))
};
div![
C![
@ -81,7 +81,7 @@ impl<'l> ToNode for ChildBuilder<'l> {
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct StyledCheckbox<'l, Options>
where
Options: Iterator<Item = ChildBuilder<'l>>,
@ -90,15 +90,23 @@ where
pub class_list: &'l str,
}
impl<'l, Options> Default for StyledCheckbox<'l, Options>
impl<'l, Options> StyledCheckbox<'l, Options>
where
Options: Iterator<Item = ChildBuilder<'l>>,
{
fn default() -> Self {
Self {
options: None,
class_list: "",
}
pub fn render(self) -> Node<Msg> {
let StyledCheckbox {
options,
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>>,
{
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>>,
}
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> {
fn default() -> Self {
Self {
@ -34,60 +81,6 @@ impl<'l> Default for StyledConfirmModal<'l> {
impl<'l> ToNode for StyledConfirmModal<'l> {
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,
}
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 {
fn into_node(self) -> Node<Msg> {
render(self)
self.render()
}
}
fn render(values: StyledDateTimeInput) -> Node<Msg> {
let timestamp = values
.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();
pub struct DayCell<'l> {
field_id: &'l FieldId,
timestamp: &'l NaiveDateTime,
current: &'l NaiveDateTime,
current_month_range: &'l RangeInclusive<NaiveDateTime>,
}
let calendar_start = 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),
};
let calendar_end = 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,
};
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(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
impl<'l> DayCell<'l> {
pub fn render(self) -> Node<Msg> {
let on_click = {
let field_id = self.field_id.clone();
let date = *self.current;
ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
Msg::StyledDateTimeInputChanged(
field_id,
StyledDateTimeChanged::DayChanged(Some(date)),
)
})
};
div![
C![
"day",
format!("{}", self.current.weekday()),
IF![self.is_selected() => "selected"],
if self.current_month_range.contains(&self.current) {
"inCurrentMonth"
} else {
"outCurrentMonth"
},
],
],
variant: TooltipVariant::DateTimeBuilder,
format!("{}", self.current.day()).as_str(),
on_click,
]
}
.into_node();
let input = {
let field_id = values.field_id.clone();
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,
]
fn is_selected(&self) -> bool {
*self.timestamp == *self.current
}
}

View File

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