Simmplify

This commit is contained in:
Adrian Woźniak 2021-04-19 10:21:20 +02:00
parent e3e55acd54
commit 1b5ac3c9c3
43 changed files with 582 additions and 739 deletions

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::Msg;
#[derive(Debug)]
@ -75,10 +74,3 @@ impl<'l> Default for StyledAvatar<'l> {
}
}
}
impl<'l> ToNode for StyledAvatar<'l> {
#[inline(always)]
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::{ButtonId, Msg};
#[allow(dead_code)]
@ -45,16 +44,13 @@ pub struct StyledButton<'l> {
}
impl<'l> StyledButton<'l> {
pub fn secondary_with_text_and_icon<I>(text: &'l str, icon: I) -> Self
where
I: ToNode,
{
pub fn secondary_with_text_and_icon(text: &'l str, icon: Node<Msg>) -> Self {
Self {
variant: ButtonVariant::Secondary,
disabled: false,
active: false,
text: Some(text),
icon: Some(icon.into_node()),
icon: Some(icon),
on_click: None,
children: vec![],
class_list: "",
@ -128,10 +124,3 @@ impl<'l> StyledButton<'l> {
]
}
}
impl<'l> ToNode for StyledButton<'l> {
#[inline(always)]
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::{FieldId, Msg};
#[derive(Debug)]
@ -121,12 +120,3 @@ where
]
}
}
impl<'l, Options> ToNode for StyledCheckbox<'l, Options>
where
Options: Iterator<Item = ChildBuilder<'l>>,
{
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -3,7 +3,6 @@ use seed::{EventHandler, *};
use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_modal::StyledModal;
use crate::shared::ToNode;
use crate::Msg;
const TITLE: &str = "Warning";
@ -63,7 +62,7 @@ impl<'l> StyledConfirmModal<'l> {
class_list: "confirmModal",
..Default::default()
}
.into_node()
.render()
}
}
@ -78,9 +77,3 @@ impl<'l> Default for StyledConfirmModal<'l> {
}
}
}
impl<'l> ToNode for StyledConfirmModal<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -6,9 +6,8 @@ use seed::prelude::*;
use seed::*;
use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::Icon;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant};
use crate::shared::ToNode;
use crate::{FieldId, Msg};
#[derive(Debug)]
@ -134,11 +133,11 @@ impl StyledDateTimeInput {
});
StyledButton {
on_click: Some(on_click_left),
icon: Some(Icon::DoubleLeft.into_node()),
icon: Some(StyledIcon::from(Icon::DoubleLeft).render()),
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node()
.render()
};
let right_action = {
let field_id = self.field_id.clone();
@ -161,7 +160,7 @@ impl StyledDateTimeInput {
});
StyledButton {
on_click: Some(on_click_right),
icon: Some(Icon::DoubleRight.into_node()),
icon: Some(StyledIcon::from(Icon::DoubleRight).render()),
variant: ButtonVariant::Empty,
..Default::default()
}
@ -256,12 +255,6 @@ impl StyledDateTimeInput {
}
}
impl ToNode for StyledDateTimeInput {
fn into_node(self) -> Node<Msg> {
self.render()
}
}
pub struct DayCell<'l> {
field_id: &'l FieldId,
timestamp: &'l NaiveDateTime,

View File

@ -2,7 +2,6 @@ use seed::prelude::*;
use seed::*;
use crate::components::styled_textarea::StyledTextarea;
use crate::shared::ToNode;
use crate::{FieldChange, FieldId, Msg};
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
@ -79,7 +78,7 @@ impl<'l> StyledEditor<'l> {
placeholder: "",
disable_auto_resize: false,
}
.into_node();
.render();
div![
C!["styledEditor"],
@ -119,13 +118,6 @@ impl<'l> StyledEditor<'l> {
}
}
impl<'l> ToNode for StyledEditor<'l> {
#[inline]
fn into_node(self) -> Node<Msg> {
self.render()
}
}
#[inline]
fn click_handler(field_id: FieldId, new_mode: Mode) -> EventHandler<Msg> {
mouse_ev(Ev::Click, move |ev| {

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::Msg;
#[derive(Debug)]
@ -41,9 +40,3 @@ impl<'l> StyledField<'l> {
]
}
}
impl<'l> ToNode for StyledField<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::Msg;
#[derive(Debug, Clone, Default)]
@ -30,10 +29,3 @@ impl<'l> StyledForm<'l> {
]
}
}
impl<'l> ToNode for StyledForm<'l> {
#[inline]
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -1,10 +1,7 @@
use std::borrow::Cow;
use jirs_data::{IssuePriority, IssueType};
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::Msg;
#[allow(dead_code)]
@ -102,14 +99,16 @@ pub enum Icon {
}
impl Icon {
pub fn to_color(&self) -> Option<String> {
#[inline(always)]
pub fn to_color(self) -> Option<String> {
match self {
Icon::Bug | Icon::Task | Icon::Story | Icon::Epic => Some(format!("var(--{})", self)),
_ => None,
}
}
pub fn to_str<'l>(&self) -> &'l str {
#[inline(always)]
pub fn to_str<'l>(self) -> &'l str {
match self {
Icon::Bug => "bug",
Icon::Stopwatch => "stopwatch",
@ -209,6 +208,7 @@ impl std::fmt::Display for Icon {
}
impl From<IssueType> for Icon {
#[inline(always)]
fn from(t: IssueType) -> Self {
use IssueType::*;
match t {
@ -220,6 +220,7 @@ impl From<IssueType> for Icon {
}
impl From<IssuePriority> for Icon {
#[inline(always)]
fn from(t: IssuePriority) -> Self {
match t {
IssuePriority::Highest => Icon::ArrowUp,
@ -232,6 +233,7 @@ impl From<IssuePriority> for Icon {
}
impl<'l> From<Icon> for StyledIcon<'l> {
#[inline(always)]
fn from(icon: Icon) -> StyledIcon<'l> {
StyledIcon {
icon,
@ -240,42 +242,31 @@ impl<'l> From<Icon> for StyledIcon<'l> {
}
}
impl ToNode for Icon {
fn into_node(self) -> Node<Msg> {
let styled: StyledIcon = self.into();
styled.into_node()
}
}
pub struct StyledIcon<'l> {
pub icon: Icon,
pub size: Option<i32>,
pub class_list: &'l str,
pub style_list: Vec<Cow<'l, str>>,
pub style_list: &'l str,
pub color: Option<&'l str>,
pub on_click: Option<EventHandler<Msg>>,
}
impl<'l> Default for StyledIcon<'l> {
#[inline(always)]
fn default() -> Self {
Self {
icon: Icon::Stopwatch,
size: None,
class_list: "",
style_list: vec![],
style_list: "",
color: None,
on_click: None,
}
}
}
impl<'l> ToNode for StyledIcon<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}
impl<'l> StyledIcon<'l> {
#[inline(always)]
pub fn render(self) -> Node<Msg> {
let StyledIcon {
icon,
@ -301,19 +292,6 @@ impl<'l> StyledIcon<'l> {
.flatten()
.collect();
let style_list = style_list.into_iter().fold("".to_string(), |mut mem, s| {
match s {
Cow::Borrowed(s) => {
mem.push_str(s);
}
Cow::Owned(owned) => {
mem.push_str(owned.as_str());
}
}
mem.push(';');
mem
});
i![
C!["styledIcon", class_list, icon.to_str()],
styles,

View File

@ -2,7 +2,6 @@ use seed::prelude::*;
use seed::*;
use web_sys::File;
use crate::shared::ToNode;
use crate::{FieldId, Msg};
#[derive(Debug, Clone)]
@ -37,13 +36,8 @@ pub struct StyledImageInput<'l> {
pub url: Option<&'l str>,
}
impl<'l> ToNode for StyledImageInput<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}
impl<'l> StyledImageInput<'l> {
#[inline(always)]
pub fn render(self) -> Node<Msg> {
let StyledImageInput {
id,

View File

@ -2,7 +2,6 @@ use seed::prelude::*;
use seed::*;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::shared::ToNode;
use crate::{FieldId, Msg};
#[derive(Clone, Debug, PartialOrd, PartialEq)]
@ -160,13 +159,6 @@ impl<'l, 'm: 'l> StyledInput<'l, 'm> {
}
}
impl<'l, 'm: 'l> ToNode for StyledInput<'l, 'm> {
#[inline]
fn into_node(self) -> Node<Msg> {
self.render()
}
}
impl<'l, 'm: 'l> StyledInput<'l, 'm> {
pub fn render(self) -> Node<Msg> {
let StyledInput {

View File

@ -3,7 +3,6 @@ use std::str::FromStr;
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::Msg;
pub struct StyledLink<'l> {
@ -12,12 +11,6 @@ pub struct StyledLink<'l> {
pub href: &'l str,
}
impl<'l> ToNode for StyledLink<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}
impl<'l> StyledLink<'l> {
pub fn render(self) -> Node<Msg> {
let StyledLink {
@ -43,7 +36,7 @@ impl<'l> StyledLink<'l> {
a![
C!["styledLink", class_list],
attrs![ At::Href => href, ],
attrs![ At::Href => href ],
on_click,
children,
]

View File

@ -2,7 +2,6 @@ use seed::prelude::*;
use seed::*;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::shared::ToNode;
use crate::Msg;
#[allow(dead_code)]
@ -61,12 +60,6 @@ impl<'l> StyledModal<'l> {
}
}
impl<'l> ToNode for StyledModal<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}
impl<'l> StyledModal<'l> {
#[inline]
pub fn render(self) -> Node<Msg> {

View File

@ -1,16 +1,12 @@
use {
crate::{
components::{
styled_button::StyledButton,
styled_icon::{Icon, StyledIcon},
styled_select::{StyledSelect, StyledSelectState},
styled_tooltip::StyledTooltip,
},
shared::{ToChild, ToNode},
ButtonId, FieldId, Msg, RteField,
},
seed::{prelude::*, *},
};
use seed::prelude::*;
use seed::*;
use crate::components::styled_button::StyledButton;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_select::{StyledSelect, StyledSelectState};
use crate::components::styled_tooltip::StyledTooltip;
use crate::shared::ToChild;
use crate::{ButtonId, FieldId, Msg, RteField};
#[derive(Debug, Clone, Copy)]
pub enum HeadingSize {
@ -437,12 +433,6 @@ impl<'component> StyledRte<'component> {
}
}
impl<'component> ToNode for StyledRte<'component> {
fn into_node(self) -> Node<Msg> {
render(self)
}
}
pub struct StyledRteBuilder<'outer> {
field_id: FieldId,
value: String,
@ -639,14 +629,14 @@ pub fn render(values: StyledRte) -> Node<Msg> {
C!["bar"],
first_row(click_handler.clone()),
second_row(&values, click_handler, change_handler),
// brush_button,
// color_bucket_button,
// color_picker_button,
// link_broken_button,
// pin_button,
// save_button,
// text_height_button,
// text_width_button,
/* brush_button,
* color_bucket_button,
* color_picker_button,
* link_broken_button,
* pin_button,
* save_button,
* text_height_button,
* text_width_button, */
],
div![
C!["editorWrapper"],
@ -852,7 +842,7 @@ fn second_row(
}))
.empty()
.build()
.into_node();
.render();
span![C!["headingOption"], button]
})
.collect();
@ -880,8 +870,8 @@ fn second_row(
C!["group font"],
// font_button,
heading_button,
// small_cap_button,
// all_caps_button
/* small_cap_button,
* all_caps_button */
]
};
@ -1000,7 +990,7 @@ fn table_tooltip(
.icon(Icon::Close)
.on_click(click_handler.clone())
.build()
.into_node();
.render();
let on_submit = click_handler;
@ -1036,7 +1026,7 @@ fn table_tooltip(
]
})
.build()
.into_node()
.render()
}
fn code_tooltip(values: &StyledRte, click_handler: EventHandler<Msg>) -> Node<Msg> {
@ -1070,7 +1060,7 @@ fn code_tooltip(values: &StyledRte, click_handler: EventHandler<Msg>) -> Node<Ms
.build(FieldId::Rte(RteField::CodeLang(Box::new(
values.field_id.clone(),
))))
.into_node();
.render();
let close_tooltip = StyledButton::build()
.empty()
@ -1078,7 +1068,7 @@ fn code_tooltip(values: &StyledRte, click_handler: EventHandler<Msg>) -> Node<Ms
.button_id(ButtonId::RteInsertCode)
.on_click(click_handler.clone())
.build()
.into_node();
.render();
let input = {
let field_id = values.field_id.clone();
@ -1098,7 +1088,7 @@ fn code_tooltip(values: &StyledRte, click_handler: EventHandler<Msg>) -> Node<Ms
.on_click(click_handler)
.text("Insert")
.build()
.into_node();
.render();
div![insert]
};
@ -1110,7 +1100,7 @@ fn code_tooltip(values: &StyledRte, click_handler: EventHandler<Msg>) -> Node<Ms
.add_child(input)
.add_child(actions)
.build()
.into_node()
.render()
}
fn styled_rte_button(
@ -1125,6 +1115,6 @@ fn styled_rte_button(
.on_click(handler)
.empty()
.build()
.into_node();
.render();
span![C!["styledRteButton"], attrs![At::Title => title], button]
}

View File

@ -5,7 +5,6 @@ use seed::*;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_select_child::*;
use crate::shared::ToNode;
use crate::{FieldId, Msg};
#[derive(Clone, Debug, PartialEq)]
@ -110,7 +109,7 @@ impl StyledSelectState {
pub struct StyledSelect<'l, Options>
where
Options: Iterator<Item = StyledSelectChild<'l>>,
Options: Iterator<Item = StyledSelectOption<'l>>,
{
pub id: FieldId,
pub variant: SelectVariant,
@ -119,7 +118,7 @@ where
pub valid: bool,
pub is_multi: bool,
pub options: Option<Options>,
pub selected: Vec<StyledSelectChild<'l>>,
pub selected: Vec<StyledSelectOption<'l>>,
pub text_filter: &'l str,
pub opened: bool,
pub clearable: bool,
@ -127,7 +126,7 @@ where
impl<'l, Options> Default for StyledSelect<'l, Options>
where
Options: Iterator<Item = StyledSelectChild<'l>>,
Options: Iterator<Item = StyledSelectOption<'l>>,
{
fn default() -> Self {
Self {
@ -146,167 +145,160 @@ where
}
}
impl<'l, Options> ToNode for StyledSelect<'l, Options>
impl<'l, Options> StyledSelect<'l, Options>
where
Options: Iterator<Item = StyledSelectChild<'l>>,
Options: Iterator<Item = StyledSelectOption<'l>>,
{
fn into_node(self) -> Node<Msg> {
render(self)
}
}
pub fn render(self) -> Node<Msg> {
let StyledSelect {
id,
variant,
dropdown_width,
name,
valid,
is_multi,
options,
selected,
text_filter,
opened,
clearable,
} = self;
pub fn render<'l, Options>(values: StyledSelect<'l, Options>) -> Node<Msg>
where
Options: Iterator<Item = StyledSelectChild<'l>>,
{
let StyledSelect {
id,
variant,
dropdown_width,
name,
valid,
is_multi,
options,
selected,
text_filter,
opened,
clearable,
} = values;
let on_text = {
let field_id = id.clone();
input_ev(Ev::KeyUp, move |value| {
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Text(value))
})
};
let on_handler = {
let field_id = id.clone();
mouse_ev(Ev::Click, move |_| {
Msg::StyledSelectChanged(field_id, StyledSelectChanged::DropDownVisibility(!opened))
})
};
let dropdown_style = dropdown_width.map_or_else(
|| "width: 100%;".to_string(),
|n| format!("width: {}px;", n),
);
let action_icon = if clearable && !selected.is_empty() {
let on_click = {
let on_text = {
let field_id = id.clone();
mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(None))
input_ev(Ev::KeyUp, move |value| {
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Text(value))
})
};
StyledIcon {
icon: Icon::Close,
class_list: "chevronIcon",
on_click: Some(on_click),
..Default::default()
}
.into_node()
} else if (selected.is_empty() || !is_multi) && variant != SelectVariant::Empty {
StyledIcon {
icon: Icon::ChevronDown,
class_list: "chevronIcon",
..Default::default()
}
.into_node()
} else {
empty![]
};
let skip = selected.iter().fold(HashMap::new(), |mut h, o| {
h.insert(o.value, true);
h
});
let children: Vec<Node<Msg>> = if let Some(options) = options {
options
.filter(|o| !skip.contains_key(&o.value) && o.match_text(text_filter))
.map(|child| {
let value = child.value();
let node = child.render_option();
let on_change = {
let field_id = id.clone();
mouse_ev(Ev::Click, move |_| {
Msg::StyledSelectChanged(
field_id,
StyledSelectChanged::Changed(Some(value)),
)
})
};
div![C!["option"], on_change, on_handler.clone(), node]
let on_handler = {
let field_id = id.clone();
mouse_ev(Ev::Click, move |_| {
Msg::StyledSelectChanged(field_id, StyledSelectChanged::DropDownVisibility(!opened))
})
.collect()
} else {
vec![]
};
};
seed::div![
C!["styledSelect", variant.to_str(), IF![!valid => "invalid"]],
attrs![At::Style => dropdown_style.as_str()],
keyboard_ev(Ev::KeyUp, |ev| {
ev.stop_propagation();
None as Option<Msg>
}),
div![
C!["valueContainer", variant.to_str()],
on_handler,
match is_multi {
true => vec![div![
C!["valueMulti"],
selected
.into_iter()
.map(|m| into_multi_value(m, id.clone()))
.collect::<Vec<Node<Msg>>>(),
IF![children.is_empty() => div![C!["placeholder"], "Select"]],
IF![!children.is_empty() => div![C!["addMore"], StyledIcon::from(Icon::Plus).render(), "Add more"]],
]],
false => selected
.into_iter()
.map(|m| m.render_value())
.collect::<Vec<Node<Msg>>>(),
},
action_icon,
],
div![
C!["dropDown"],
attrs![At::Style => dropdown_style.as_str()],
IF![opened => seed::input![
C!["dropDownInput"],
attrs![
At::Name => name,
At::Type => "text"
At::Placeholder => "Search"
At::AutoFocus => "true",
],
on_text,
]],
match (opened, children.is_empty()) {
(false, _) => empty![],
(_, true) => seed::div![C!["noOptions"], "No results"],
_ => seed::div![C!["options"], children],
let dropdown_style = dropdown_width.map_or_else(
|| "width: 100%;".to_string(),
|n| format!("width: {}px;", n),
);
let action_icon = if clearable && !selected.is_empty() {
let on_click = {
let field_id = id.clone();
mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(None))
})
};
StyledIcon {
icon: Icon::Close,
class_list: "chevronIcon",
on_click: Some(on_click),
..Default::default()
}
.render()
} else if (selected.is_empty() || !is_multi) && variant != SelectVariant::Empty {
StyledIcon {
icon: Icon::ChevronDown,
class_list: "chevronIcon",
..Default::default()
}
.render()
} else {
empty![]
};
let skip = selected.iter().fold(HashMap::new(), |mut h, o| {
h.insert(o.value, true);
h
});
let children: Vec<Node<Msg>> = if let Some(options) = options {
options
.filter(|o| !skip.contains_key(&o.value) && o.match_text(text_filter))
.map(|child| {
let value = child.value();
let node = child.render_option();
let on_change = {
let field_id = id.clone();
mouse_ev(Ev::Click, move |_| {
Msg::StyledSelectChanged(
field_id,
StyledSelectChanged::Changed(Some(value)),
)
})
};
div![C!["option"], on_change, on_handler.clone(), node]
})
.collect()
} else {
vec![]
};
seed::div![
C!["styledSelect", variant.to_str(), IF![!valid => "invalid"]],
attrs![At::Style => dropdown_style.as_str()],
keyboard_ev(Ev::KeyUp, |ev| {
ev.stop_propagation();
None as Option<Msg>
}),
div![
C!["valueContainer", variant.to_str()],
on_handler,
match is_multi {
true => vec![div![
C!["valueMulti"],
selected
.into_iter()
.map(|m| Self::multi_value(m, id.clone()))
.collect::<Vec<Node<Msg>>>(),
IF![children.is_empty() => div![C!["placeholder"], "Select"]],
IF![!children.is_empty() => div![C!["addMore"], StyledIcon::from(Icon::Plus).render(), "Add more"]],
]],
false => selected
.into_iter()
.map(|m| m.render_value())
.collect::<Vec<Node<Msg>>>(),
},
action_icon,
],
div![
C!["dropDown"],
attrs![At::Style => dropdown_style.as_str()],
IF![opened => seed::input![
C!["dropDownInput"],
attrs![
At::Name => name,
At::Type => "text"
At::Placeholder => "Search"
At::AutoFocus => "true",
],
on_text,
]],
match (opened, children.is_empty()) {
(false, _) => empty![],
(_, true) => seed::div![C!["noOptions"], "No results"],
_ => seed::div![C!["options"], children],
}
]
]
]
}
fn into_multi_value(child: StyledSelectChild, id: FieldId) -> Node<Msg> {
let value = child.value();
let opt = child.render_multi_value();
let handler = {
let field_id = id;
mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value))
})
};
div![C!["valueMultiItem"], opt, handler]
}
fn multi_value(child: StyledSelectOption, id: FieldId) -> Node<Msg> {
let value = child.value();
let opt = child.render_multi_value();
let handler = {
let field_id = id;
mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value))
})
};
div![C!["valueMultiItem"], opt, handler]
}
}

View File

@ -3,7 +3,6 @@ use seed::*;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_select::SelectVariant;
use crate::shared::ToNode;
use crate::Msg;
#[derive(Copy, Clone, PartialEq)]
@ -14,7 +13,7 @@ pub enum DisplayType {
SelectMultiValue,
}
pub struct StyledSelectChild<'l> {
pub struct StyledSelectOption<'l> {
pub name: Option<&'l str>,
pub icon: Option<Node<Msg>>,
pub text: Option<&'l str>,
@ -23,7 +22,7 @@ pub struct StyledSelectChild<'l> {
pub variant: SelectVariant,
}
impl<'l> Default for StyledSelectChild<'l> {
impl<'l> Default for StyledSelectOption<'l> {
fn default() -> Self {
Self {
name: None,
@ -36,25 +35,25 @@ impl<'l> Default for StyledSelectChild<'l> {
}
}
impl<'l> StyledSelectChild<'l> {
#[inline]
impl<'l> StyledSelectOption<'l> {
#[inline(always)]
pub fn value(&self) -> u32 {
self.value
}
#[inline(always)]
pub fn render_value(self) -> Node<Msg> {
render(DisplayType::SelectValue, self)
self.render(DisplayType::SelectValue)
}
#[inline(always)]
pub fn render_multi_value(self) -> Node<Msg> {
render(DisplayType::SelectMultiValue, self)
self.render(DisplayType::SelectMultiValue)
}
#[inline(always)]
pub fn render_option(self) -> Node<Msg> {
render(DisplayType::SelectOption, self)
self.render(DisplayType::SelectOption)
}
#[inline(always)]
@ -64,56 +63,57 @@ impl<'l> StyledSelectChild<'l> {
.map(|t| t.to_lowercase().contains(text.to_lowercase().as_str()))
.unwrap_or(true)
}
}
#[inline(always)]
pub fn render(display_type: DisplayType, values: StyledSelectChild) -> Node<Msg> {
let StyledSelectChild {
name,
icon,
text,
value: _,
class_list,
variant,
} = values;
#[inline(always)]
pub fn render(self, display_type: DisplayType) -> Node<Msg> {
let StyledSelectOption {
name,
icon,
text,
value: _,
class_list,
variant,
} = self;
let icon_node = match icon {
Some(icon) => icon,
_ => empty![],
};
let icon_node = match icon {
Some(icon) => icon,
_ => empty![],
};
let label_node = match text {
Some(text) => div![
let label_node = match text {
Some(text) => div![
C![
variant.to_str(),
name.as_deref()
.map(|s| format!("{}Label", s))
.unwrap_or_default(),
match display_type {
DisplayType::SelectOption => "optionLabel",
DisplayType::SelectValue | DisplayType::SelectMultiValue =>
"selectItemLabel",
},
class_list
],
text
],
_ => empty![],
};
div![
C![
variant.to_str(),
name.as_deref()
.map(|s| format!("{}Label", s))
.unwrap_or_default(),
name.as_deref().unwrap_or_default(),
match display_type {
DisplayType::SelectOption => "optionLabel",
DisplayType::SelectValue | DisplayType::SelectMultiValue => "selectItemLabel",
DisplayType::SelectOption => "optionItem",
DisplayType::SelectValue | DisplayType::SelectMultiValue => "selectItem value",
},
class_list
],
text
],
_ => empty![],
};
div![
C![
variant.to_str(),
name.as_deref().unwrap_or_default(),
match display_type {
DisplayType::SelectOption => "optionItem",
DisplayType::SelectValue | DisplayType::SelectMultiValue => "selectItem value",
},
class_list
],
icon_node,
label_node,
IF![display_type == DisplayType::SelectMultiValue => close_icon()]
]
icon_node,
label_node,
IF![display_type == DisplayType::SelectMultiValue => close_icon()]
]
}
}
#[inline(always)]
@ -123,5 +123,5 @@ fn close_icon() -> Node<Msg> {
size: Some(14),
..Default::default()
}
.into_node()
.render()
}

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::{FieldId, Msg};
#[derive(Debug)]
@ -31,9 +30,105 @@ impl<'l> Default for StyledTextarea<'l> {
}
}
impl<'l> ToNode for StyledTextarea<'l> {
fn into_node(self) -> Node<Msg> {
render(self)
impl<'l> StyledTextarea<'l> {
// height = `calc( (${$0.value.split("\n").length}px * ( 15 * 1.4285 )) + 17px +
// 2px)` where:
// * 15 is font-size
// * 1.4285 is line-height
// * 17 is padding top + bottom
// * 2 is border top + bottom
pub fn render(self) -> Node<Msg> {
let StyledTextarea {
id,
height,
max_height,
value,
class_list,
update_event,
placeholder,
disable_auto_resize,
} = self;
let id = id.expect("Text area requires FieldId");
let mut style_list = vec![];
let min_height = get_min_height(value, height as f64, disable_auto_resize);
if min_height > 0f64 {
style_list.push(format!("min-height: {}px", min_height));
}
if max_height > 0 {
style_list.push(format!("max-height: {}px", max_height));
}
if disable_auto_resize {
style_list.push(format!(
"resize: none; height: {h}px; max-height: {h}px; min-height: {h}px",
h = max_height
));
}
let handler_disable_auto_resize = disable_auto_resize;
let resize_handler = ev(Ev::Change, move |event| {
event.stop_propagation();
if handler_disable_auto_resize {
return None as Option<Msg>;
}
let target = event.target().unwrap();
let textarea = seed::to_textarea(&target);
let value = textarea.value();
let min_height =
get_min_height(value.as_str(), height as f64, handler_disable_auto_resize);
textarea
.style()
.set_css_text(format!("height: {min_height}px", min_height = min_height).as_str());
None as Option<Msg>
});
let handler_disable_auto_resize = disable_auto_resize;
let text_input_handler = {
let id = id.clone();
ev(update_event, move |event| {
event.stop_propagation();
let value = event
.target()
.map(|target| seed::to_textarea(&target).value())
.unwrap_or_default();
if handler_disable_auto_resize && value.contains('\n') {
event.prevent_default();
}
Some(Msg::StrInputChanged(
id,
if handler_disable_auto_resize {
value.trim().to_string()
} else {
value
},
))
})
};
div![
id![format!("styledTextArea-{}", id)],
C!["styledTextArea"],
div![C!["textAreaHeading"]],
textarea![
C![class_list, "textAreaInput"],
attrs![
At::AutoFocus => "true";
At::Style => style_list.join(";");
At::Placeholder => placeholder;
At::Rows => if disable_auto_resize { "5" } else { "auto" }
],
value,
resize_handler,
text_input_handler,
]
]
}
}
@ -44,105 +139,6 @@ const PADDING_TOP_BOTTOM: f64 = 17f64;
const BORDER_TOP_BOTTOM: f64 = 2f64;
const ADDITIONAL_HEIGHT: f64 = PADDING_TOP_BOTTOM + BORDER_TOP_BOTTOM;
// height = `calc( (${$0.value.split("\n").length}px * ( 15 * 1.4285 )) + 17px +
// 2px)` where:
// * 15 is font-size
// * 1.4285 is line-height
// * 17 is padding top + bottom
// * 2 is border top + bottom
pub fn render(values: StyledTextarea) -> Node<Msg> {
let StyledTextarea {
id,
height,
max_height,
value,
class_list,
update_event,
placeholder,
disable_auto_resize,
} = values;
let id = id.expect("Text area requires FieldId");
let mut style_list = vec![];
let min_height = get_min_height(value, height as f64, disable_auto_resize);
if min_height > 0f64 {
style_list.push(format!("min-height: {}px", min_height));
}
if max_height > 0 {
style_list.push(format!("max-height: {}px", max_height));
}
if disable_auto_resize {
style_list.push(format!(
"resize: none; height: {h}px; max-height: {h}px; min-height: {h}px",
h = max_height
));
}
let handler_disable_auto_resize = disable_auto_resize;
let resize_handler = ev(Ev::Change, move |event| {
event.stop_propagation();
if handler_disable_auto_resize {
return None as Option<Msg>;
}
let target = event.target().unwrap();
let textarea = seed::to_textarea(&target);
let value = textarea.value();
let min_height = get_min_height(value.as_str(), height as f64, handler_disable_auto_resize);
textarea
.style()
.set_css_text(format!("height: {min_height}px", min_height = min_height).as_str());
None as Option<Msg>
});
let handler_disable_auto_resize = disable_auto_resize;
let text_input_handler = {
let id = id.clone();
ev(update_event, move |event| {
event.stop_propagation();
let value = event
.target()
.map(|target| seed::to_textarea(&target).value())
.unwrap_or_default();
if handler_disable_auto_resize && value.contains('\n') {
event.prevent_default();
}
Some(Msg::StrInputChanged(
id,
if handler_disable_auto_resize {
value.trim().to_string()
} else {
value
},
))
})
};
div![
id![format!("styledTextArea-{}", id)],
C!["styledTextArea"],
div![C!["textAreaHeading"]],
textarea![
C![class_list, "textAreaInput"],
attrs![
At::AutoFocus => "true";
At::Style => style_list.join(";");
At::Placeholder => placeholder;
At::Rows => if disable_auto_resize { "5" } else { "auto" }
],
value,
resize_handler,
text_input_handler,
]
]
}
fn get_min_height(value: &str, min_height: f64, disable_auto_resize: bool) -> f64 {
if disable_auto_resize {
return min_height;

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use seed::*;
use crate::shared::ToNode;
use crate::Msg;
#[derive(Debug, Copy, Clone)]
@ -59,9 +58,3 @@ impl<'l> StyledTooltip<'l> {
}
}
}
impl<'l> ToNode for StyledTooltip<'l> {
fn into_node(self) -> Node<Msg> {
self.render()
}
}

View File

@ -1,7 +1,6 @@
use jirs_data::CommentId;
use seed::prelude::*;
use crate::shared::ToNode;
use crate::styled_confirm_modal::StyledConfirmModal;
use crate::{model, Msg};
@ -14,5 +13,5 @@ pub fn view(_model: &model::Model, modal: &super::Model) -> Node<Msg> {
on_confirm: Some(mouse_ev(Ev::Click, move |_| Msg::DeleteComment(comment_id))),
..Default::default()
}
.into_node()
.render()
}

View File

@ -3,7 +3,6 @@ use seed::*;
use crate::components::styled_modal::StyledModal;
use crate::model::Model;
use crate::shared::ToNode;
use crate::Msg;
pub fn view(model: &Model) -> Node<Msg> {
@ -15,5 +14,5 @@ pub fn view(model: &Model) -> Node<Msg> {
children: vec![code],
..Default::default()
}
.into_node()
.render()
}

View File

@ -3,9 +3,8 @@ use seed::prelude::Node;
use crate::components::styled_field::StyledField;
use crate::components::styled_select::{SelectVariant, StyledSelect};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::model::{IssueModal, Model};
use crate::shared::ToNode;
use crate::{FieldId, Msg};
pub fn epic_field<Modal>(model: &Model, modal: &Modal, field_id: FieldId) -> Option<Node<Msg>>
@ -31,7 +30,7 @@ where
valid: true,
..Default::default()
}
.into_node();
.render();
Some(
StyledField {
label: "Epic",
@ -39,12 +38,12 @@ where
input,
..Default::default()
}
.into_node(),
.render(),
)
}
fn epic_select_option<'l>(epic: &'l Epic) -> StyledSelectChild<'l> {
StyledSelectChild {
fn epic_select_option<'l>(epic: &'l Epic) -> StyledSelectOption<'l> {
StyledSelectOption {
value: epic.id as u32,
text: Some(epic.name.as_str()),
..Default::default()

View File

@ -6,7 +6,6 @@ use crate::components::styled_confirm_modal::*;
use crate::components::styled_icon::*;
use crate::components::styled_modal::*;
use crate::modals::epics_delete::Model;
use crate::shared::ToNode;
use crate::{model, Msg};
pub fn view(model: &model::Model, modal: &Model) -> Node<Msg> {
@ -22,7 +21,7 @@ pub fn view(model: &model::Model, modal: &Model) -> Node<Msg> {
})),
..Default::default()
}
.into_node()
.render()
} else {
StyledModal {
children: vec![warning(model, modal)],
@ -30,7 +29,7 @@ pub fn view(model: &model::Model, modal: &Model) -> Node<Msg> {
class_list: "deleteEpic",
..Default::default()
}
.into_node()
.render()
}
}
@ -40,7 +39,7 @@ fn warning(model: &model::Model, modal: &Model) -> Node<Msg> {
.iter()
.flat_map(|id| model.issues_by_id.get(id))
.map(|issue| {
let link = StyledIcon::from(Icon::Link).into_node();
let link = StyledIcon::from(Icon::Link).render();
li![div![
C!["relatedIssue"],
a![
@ -62,7 +61,7 @@ fn warning(model: &model::Model, modal: &Model) -> Node<Msg> {
variant: ButtonVariant::Secondary,
..Default::default()
}
.into_node();
.render();
section![
h3![C!["header"], "Cannot delete epic"],

View File

@ -4,11 +4,10 @@ use seed::*;
use crate::components::styled_button::*;
use crate::components::styled_checkbox::*;
use crate::components::styled_icon::Icon;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_input::*;
use crate::components::styled_modal::*;
use crate::modals::epics_edit::Model;
use crate::shared::ToNode;
use crate::{model, FieldId, Msg};
pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
@ -24,10 +23,10 @@ pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
Msg::ModalDropped
})),
variant: ButtonVariant::Empty,
icon: Some(Icon::Close.into_node()),
icon: Some(StyledIcon::from(Icon::Close).render()),
..Default::default()
}
.into_node();
.render();
StyledModal {
width: Some(600),
class_list: "editEpic",
@ -39,12 +38,12 @@ pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
id: Some(FieldId::EditEpic(EpicFieldId::Name)),
..Default::default()
}
.into_node(),
.render(),
transform,
],
..Default::default()
}
.into_node()
.render()
}
fn transform_into_available(modal: &super::Model) -> Node<Msg> {
@ -56,7 +55,7 @@ fn transform_into_available(modal: &super::Model) -> Node<Msg> {
),
..Default::default()
}
.into_node();
.render();
let execute = StyledButton {
on_click: Some(mouse_ev("click", |ev| {
ev.stop_propagation();
@ -66,7 +65,7 @@ fn transform_into_available(modal: &super::Model) -> Node<Msg> {
text: Some("Transform"),
..Default::default()
}
.into_node();
.render();
div![C!["transform available"], div![types], div![execute]]
}

View File

@ -2,7 +2,6 @@ use jirs_data::IssueStatusId;
use seed::prelude::*;
use crate::components::styled_confirm_modal::StyledConfirmModal;
use crate::shared::ToNode;
use crate::{model, Msg};
pub fn view(_model: &model::Model, issue_status_id: IssueStatusId) -> Node<Msg> {
@ -15,5 +14,5 @@ pub fn view(_model: &model::Model, issue_status_id: IssueStatusId) -> Node<Msg>
Msg::DeleteIssueStatus(issue_status_id)
})),
}
.into_node()
.render()
}

View File

@ -11,12 +11,11 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_input::StyledInput;
use crate::components::styled_modal::StyledModal;
use crate::components::styled_select::{SelectVariant, StyledSelect};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::components::styled_textarea::StyledTextarea;
use crate::modals::epic_field;
use crate::modals::issues_create::{Model as AddIssueModal, Type};
use crate::model::Model;
use crate::shared::ToNode;
use crate::{FieldId, Msg};
pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
@ -45,11 +44,11 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
popup_visible: modal.epic_starts_at_state.popup_visible,
timestamp: modal.epic_starts_at_state.timestamp.clone(),
}
.into_node(),
.render(),
label: "Starts at",
..Default::default()
}
.into_node();
.render();
let end = StyledField {
input: StyledDateTimeInput {
@ -57,11 +56,11 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
popup_visible: modal.epic_ends_at_state.popup_visible,
timestamp: modal.epic_ends_at_state.timestamp.clone(),
}
.into_node(),
.render(),
label: "Ends at",
..Default::default()
}
.into_node();
.render();
form.fields.push(name_field);
form.fields.push(starts);
@ -99,7 +98,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
})),
..Default::default()
}
.into_node()
.render()
};
let cancel = StyledButton {
variant: ButtonVariant::Empty,
@ -112,7 +111,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
})),
..Default::default()
}
.into_node();
.render();
let actions = div![attrs![At::Class => "actions"], submit, cancel];
form.fields.push(actions);
@ -120,10 +119,10 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
StyledModal {
class_list: "addIssue",
width: Some(0),
children: vec![form.into_node()],
children: vec![form.render()],
..Default::default()
}
.into_node()
.render()
}
fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
@ -146,18 +145,18 @@ fn issue_type_field(modal: &AddIssueModal) -> Node<Msg> {
)],
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Issue Type",
tip: Some("Start typing to get a list of possible matches."),
input: select_type,
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn type_select_option<'l>(t: Type) -> StyledSelectChild<'l> {
fn type_select_option<'l>(t: Type) -> StyledSelectOption<'l> {
let name = match t {
Type::Task => "Task",
Type::Bug => "Bug",
@ -165,7 +164,7 @@ fn type_select_option<'l>(t: Type) -> StyledSelectChild<'l> {
Type::Epic => "Epic",
};
StyledSelectChild {
StyledSelectOption {
class_list: name,
text: Some(name),
icon: Some(
@ -179,7 +178,7 @@ fn type_select_option<'l>(t: Type) -> StyledSelectChild<'l> {
class_list: name,
..Default::default()
}
.into_node(),
.render(),
),
name: Some("type"),
value: t.into(),
@ -195,14 +194,14 @@ fn short_summary_field(modal: &AddIssueModal) -> Node<Msg> {
id: Some(FieldId::AddIssueModal(IssueFieldId::Title)),
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Short Summary",
tip: Some("Concisely summarize the issue in one or two sentences."),
input: short_summary,
..Default::default()
}
.into_node()
.render()
}
fn description_field() -> Node<Msg> {
@ -212,14 +211,14 @@ fn description_field() -> Node<Msg> {
class_list: "textarea",
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Description",
tip: Some("Describe the issue in as much detail as you'd like."),
input: description,
..Default::default()
}
.into_node()
.render()
}
fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
@ -248,18 +247,18 @@ fn reporter_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
valid: true,
..Default::default()
}
.into_node();
.render();
StyledField {
input: reporter,
label: "Reporter",
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn reporter_select_option(user: &User) -> StyledSelectChild {
StyledSelectChild {
fn reporter_select_option(user: &User) -> StyledSelectOption {
StyledSelectOption {
value: user.id as u32,
icon: Some(
StyledAvatar {
@ -268,7 +267,7 @@ fn reporter_select_option(user: &User) -> StyledSelectChild {
avatar_url: user.avatar_url.as_deref(),
..StyledAvatar::default()
}
.into_node(),
.render(),
),
text: Some(user.name.as_str()),
name: Some("reporter"),
@ -299,19 +298,19 @@ fn assignees_field(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
valid: true,
..Default::default()
}
.into_node();
.render();
StyledField {
input: assignees,
label: "Assignees",
tip: Some(""),
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn assignee_select_option(user: &User) -> StyledSelectChild {
StyledSelectChild {
fn assignee_select_option(user: &User) -> StyledSelectOption {
StyledSelectOption {
value: user.id as u32,
icon: Some(
StyledAvatar {
@ -320,7 +319,7 @@ fn assignee_select_option(user: &User) -> StyledSelectChild {
avatar_url: user.avatar_url.as_deref(),
..StyledAvatar::default()
}
.into_node(),
.render(),
),
text: Some(user.name.as_str()),
name: Some("assignees"),
@ -341,25 +340,25 @@ fn issue_priority_field(modal: &AddIssueModal) -> Node<Msg> {
selected: vec![priority_select_option(modal.priority)],
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Issue Type",
tip: Some("Priority in relation to other issues."),
input: select_priority,
..Default::default()
}
.into_node()
.render()
}
fn priority_select_option<'l>(priority: IssuePriority) -> StyledSelectChild<'l> {
StyledSelectChild {
fn priority_select_option<'l>(priority: IssuePriority) -> StyledSelectOption<'l> {
StyledSelectOption {
icon: Some(
StyledIcon {
icon: priority.clone().into(),
class_list: priority.to_str(),
..Default::default()
}
.into_node(),
.render(),
),
text: Some(priority.to_str()),
class_list: priority.to_str(),
@ -376,12 +375,12 @@ fn name_field(modal: &AddIssueModal) -> Node<Msg> {
id: Some(FieldId::AddIssueModal(IssueFieldId::Title)),
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Epic name",
tip: Some("Describe upcoming feature."),
input: name,
..Default::default()
}
.into_node()
.render()
}

View File

@ -1,7 +1,6 @@
use seed::prelude::*;
use crate::components::styled_confirm_modal::StyledConfirmModal;
use crate::shared::ToNode;
use crate::{model, Msg};
pub fn view(model: &model::Model) -> Node<Msg> {
@ -17,5 +16,5 @@ pub fn view(model: &model::Model) -> Node<Msg> {
cancel_text: "Cancel",
on_confirm: Some(mouse_ev(Ev::Click, move |_| Msg::DeleteIssue(issue_id))),
}
.into_node()
.render()
}

View File

@ -14,13 +14,12 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_input::StyledInput;
use crate::components::styled_modal::*;
use crate::components::styled_select::{SelectVariant, StyledSelect, StyledSelectState};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::modals::epic_field;
use crate::modals::issues_edit::Model as EditIssueModal;
use crate::modals::time_tracking::time_tracking_field;
use crate::model::{ModalType, Model};
use crate::shared::tracking_widget::tracking_link;
use crate::shared::ToNode;
use crate::{EditIssueModalSection, FieldChange, FieldId, Msg};
mod comments;
@ -30,7 +29,7 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
.issues_by_id
.get(&modal.id)
.map(|_issue| {
StyledModal::centered_with_width_and_body(1040, vec![details(model, modal)]).into_node()
StyledModal::centered_with_width_and_body(1040, vec![details(model, modal)]).render()
})
.unwrap_or(Node::Empty)
}
@ -96,7 +95,7 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
let copy_button = StyledButton {
variant: ButtonVariant::Empty,
icon: Some(Icon::Link.into_node()),
icon: Some(StyledIcon::from(Icon::Link).render()),
on_click: Some(click_handler),
children: vec![span![if *link_copied {
"Link Copied"
@ -105,7 +104,7 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
}]],
..Default::default()
}
.into_node();
.render();
let delete_button = StyledButton {
variant: ButtonVariant::Empty,
icon: Some(
@ -114,12 +113,12 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
size: Some(19),
..Default::default()
}
.into_node(),
.render(),
),
on_click: Some(delete_confirmation_handler),
..Default::default()
}
.into_node();
.render();
let close_button = StyledButton {
variant: ButtonVariant::Empty,
icon: Some(
@ -128,12 +127,12 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
size: Some(24),
..Default::default()
}
.into_node(),
.render(),
),
on_click: Some(close_handler),
..Default::default()
}
.into_node();
.render();
let issue_type_select = {
let id = modal.id;
@ -155,7 +154,7 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
selected: vec![type_select_option(payload.issue_type, &text)],
..Default::default()
}
.into_node()
.render()
};
div![
@ -171,18 +170,18 @@ fn modal_header(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
}
#[inline(always)]
fn type_select_option<'l>(t: IssueType, text: &'l str) -> StyledSelectChild<'l> {
fn type_select_option<'l>(t: IssueType, text: &'l str) -> StyledSelectOption<'l> {
let name = t.to_label();
StyledSelectChild {
StyledSelectOption {
class_list: name,
text: Some(text),
icon: Some(
StyledIcon {
icon: t.clone().into(),
icon: t.into(),
class_list: name,
..Default::default()
}
.into_node(),
.render(),
),
value: t.into(),
name: Some("type"),
@ -208,7 +207,7 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
))),
..Default::default()
}
.into_node();
.render();
let description = {
StyledEditor {
@ -221,13 +220,13 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
mode: description_state.mode.clone(),
update_event: Ev::Change,
}
.into_node()
.render()
};
let description_field = StyledField {
input: description,
..Default::default()
}
.into_node();
.render();
let user_avatar = StyledAvatar {
avatar_url: model.user.as_ref().and_then(|u| u.avatar_url.as_deref()),
@ -235,7 +234,7 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
class_list: "userAvatar",
..StyledAvatar::default()
}
.into_node();
.render();
let create_comment = if comment_form.creating && comment_form.id.is_none() {
build_comment_form(comment_form)
@ -320,7 +319,7 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
input: tracking,
..Default::default()
}
.into_node();
.render();
(estimate_field, tracking_field)
} else {
(Node::Empty, Node::Empty)
@ -364,25 +363,25 @@ fn priorities_select(
selected: vec![priority_select_option(payload.priority)],
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Priority",
input: priority,
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn priority_select_option<'l>(ip: IssuePriority) -> StyledSelectChild<'l> {
StyledSelectChild {
fn priority_select_option<'l>(ip: IssuePriority) -> StyledSelectOption<'l> {
StyledSelectOption {
icon: Some(
StyledIcon {
icon: ip.clone().into(),
class_list: ip.to_str(),
..Default::default()
}
.into_node(),
.render(),
),
text: Some(ip.to_str()),
class_list: ip.to_str(),
@ -415,18 +414,18 @@ fn status_select(
valid: true,
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Status",
input: status,
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn issue_status_select_option<'l>(is: &'l IssueStatus) -> StyledSelectChild<'l> {
StyledSelectChild {
fn issue_status_select_option<'l>(is: &'l IssueStatus) -> StyledSelectOption<'l> {
StyledSelectOption {
value: is.id as u32,
class_list: is.name.as_str(),
text: Some(is.name.as_str()),
@ -456,18 +455,18 @@ fn reporters_select(
.collect(),
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Reporter",
input: reporter,
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn reporter_select_option<'l>(user: &'l User) -> StyledSelectChild<'l> {
StyledSelectChild {
fn reporter_select_option<'l>(user: &'l User) -> StyledSelectOption<'l> {
StyledSelectOption {
value: user.id as u32,
icon: Some(
StyledAvatar {
@ -476,7 +475,7 @@ fn reporter_select_option<'l>(user: &'l User) -> StyledSelectChild<'l> {
avatar_url: user.avatar_url.as_deref(),
..StyledAvatar::default()
}
.into_node(),
.render(),
),
text: Some(user.name.as_str()),
name: Some("reporter"),
@ -506,18 +505,18 @@ fn assignees_select(
.collect(),
..Default::default()
}
.into_node();
.render();
StyledField {
input: assignees,
label: "Assignees",
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn assignee_select_option<'l>(user: &'l User) -> StyledSelectChild<'l> {
StyledSelectChild {
fn assignee_select_option<'l>(user: &'l User) -> StyledSelectOption<'l> {
StyledSelectOption {
value: user.id as u32,
icon: Some(
StyledAvatar {
@ -526,7 +525,7 @@ fn assignee_select_option<'l>(user: &'l User) -> StyledSelectChild<'l> {
avatar_url: user.avatar_url.as_deref(),
..StyledAvatar::default()
}
.into_node(),
.render(),
),
text: Some(user.name.as_str()),
name: Some("assignees"),

View File

@ -7,9 +7,9 @@ use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_textarea::StyledTextarea;
use crate::modals::issues_edit::Model as EditIssueModal;
use crate::model::{CommentForm, ModalType, Model};
use crate::shared::ToNode;
use crate::{EditIssueModalSection, FieldChange, FieldId, Msg};
#[inline(always)]
pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
let submit_comment_form = mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
@ -31,7 +31,7 @@ pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
placeholder: "Add a comment...",
..Default::default()
}
.into_node();
.render();
let submit = StyledButton {
variant: ButtonVariant::Primary,
@ -39,18 +39,19 @@ pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
text: Some("Save"),
..Default::default()
}
.into_node();
.render();
let cancel = StyledButton {
variant: ButtonVariant::Empty,
on_click: Some(close_comment_form),
text: Some("Cancel"),
..Default::default()
}
.into_node();
.render();
vec![text_area, div![C!["actions"], submit, cancel]]
}
#[inline(always)]
pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Option<Node<Msg>> {
let show_form = modal.comment_form.creating && modal.comment_form.id == Some(comment.id);
@ -62,7 +63,7 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
class_list: "userAvatar",
..StyledAvatar::default()
}
.into_node();
.render();
let buttons = if model.user.as_ref().map(|u| u.id) == Some(comment.user_id) {
let comment_id = comment.id;
@ -82,7 +83,7 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node();
.render();
let cancel_button = StyledButton {
class_list: "deleteButton",
@ -91,7 +92,7 @@ pub fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Opti
variant: ButtonVariant::Empty,
..Default::default()
}
.into_node();
.render();
vec![edit_button, cancel_button]
} else {

View File

@ -7,10 +7,9 @@ use crate::components::styled_field::StyledField;
use crate::components::styled_input::{StyledInput, StyledInputState};
use crate::components::styled_modal::StyledModal;
use crate::components::styled_select::{StyledSelect, StyledSelectState};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::model::Model;
use crate::shared::tracking_widget::{fibonacci_value_name, fibonacci_values, tracking_widget};
use crate::shared::ToNode;
use crate::{EditIssueModalSection, FieldId, Msg};
pub fn value_for_time_tracking(v: &Option<i32>, time_tracking_type: &TimeTracking) -> String {
@ -69,7 +68,7 @@ pub fn view(model: &Model, modal: &super::Model) -> Node<Msg> {
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ModalDropped)),
..Default::default()
}
.into_node();
.render();
StyledModal {
class_list: "timeTrackingModal",
@ -77,7 +76,7 @@ pub fn view(model: &Model, modal: &super::Model) -> Node<Msg> {
width: Some(400),
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
@ -110,27 +109,27 @@ pub fn time_tracking_field(
),
..Default::default()
}
.into_node(),
.render(),
TimeTracking::Hourly => StyledInput {
valid: input_state.is_valid(),
value: input_state.value.as_str(),
id: Some(field_id),
..Default::default()
}
.into_node(),
.render(),
};
StyledField {
input,
label,
..Default::default()
}
.into_node()
.render()
}
fn fibonacci_value_select_option<'l>(value: u32) -> StyledSelectChild<'l> {
fn fibonacci_value_select_option<'l>(value: u32) -> StyledSelectOption<'l> {
let name = fibonacci_value_name(value);
StyledSelectChild {
StyledSelectOption {
class_list: name,
text: Some(name),
value,

View File

@ -8,7 +8,7 @@ use crate::components::styled_form::StyledForm;
use crate::components::styled_input::StyledInput;
use crate::model::{Model, PageContent};
use crate::pages::invite_page::InvitePage;
use crate::shared::{outer_layout, ToNode};
use crate::shared::outer_layout;
use crate::validations::is_token;
use crate::{match_page, FieldId, InvitationPageChange, Msg, PageChanged};
@ -30,7 +30,7 @@ pub fn view(model: &Model) -> Node<Msg> {
})),
fields: vec![token_field, submit_field, error],
}
.into_node();
.render();
outer_layout(model, "invite", vec![form])
}
@ -41,12 +41,12 @@ fn submit(_page: &InvitePage) -> Node<Msg> {
variant: ButtonVariant::Primary,
..Default::default()
}
.into_node();
.render();
StyledField {
input: submit,
..Default::default()
}
.into_node()
.render()
}
fn token_field(page: &InvitePage) -> Node<Msg> {
@ -56,12 +56,12 @@ fn token_field(page: &InvitePage) -> Node<Msg> {
value: page.token.as_str(),
..Default::default()
}
.into_node();
.render();
StyledField {
input,
label: "Your invite token",
..Default::default()
}
.into_node()
.render()
}

View File

@ -10,10 +10,10 @@ use crate::components::styled_form::StyledForm;
use crate::components::styled_image_input::StyledImageInput;
use crate::components::styled_input::{InputVariant, StyledInput};
use crate::components::styled_select::{SelectVariant, StyledSelect};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::model::{Model, PageContent};
use crate::pages::profile_page::model::ProfilePage;
use crate::shared::{inner_layout, ToNode};
use crate::shared::inner_layout;
use crate::{FieldId, Msg, PageChanged, ProfilePageChange};
pub fn view(model: &Model) -> Node<Msg> {
@ -27,7 +27,7 @@ pub fn view(model: &Model) -> Node<Msg> {
class_list: "avatar",
url: page.avatar.url.as_deref(),
}
.into_node();
.render();
let username = StyledInput {
id: Some(FieldId::Profile(UsersFieldId::Username)),
@ -36,13 +36,13 @@ pub fn view(model: &Model) -> Node<Msg> {
variant: InputVariant::Primary,
..Default::default()
}
.into_node();
.render();
let username_field = StyledField {
label: "Username",
input: username,
..Default::default()
}
.into_node();
.render();
let email = StyledInput {
id: Some(FieldId::Profile(UsersFieldId::Email)),
@ -51,13 +51,13 @@ pub fn view(model: &Model) -> Node<Msg> {
variant: InputVariant::Primary,
..Default::default()
}
.into_node();
.render();
let email_field = StyledField {
label: "E-Mail",
input: email,
..Default::default()
}
.into_node();
.render();
let current_project = build_current_project(model, page);
@ -70,12 +70,12 @@ pub fn view(model: &Model) -> Node<Msg> {
})),
..Default::default()
}
.into_node();
.render();
let submit_field = StyledField {
input: submit,
..Default::default()
}
.into_node();
.render();
let content = StyledForm {
heading: "Profile",
@ -91,7 +91,7 @@ pub fn view(model: &Model) -> Node<Msg> {
submit_field,
],
}
.into_node();
.render();
inner_layout(model, "profile", &[content])
}
@ -137,18 +137,18 @@ fn build_current_project(model: &Model, page: &ProfilePage) -> Node<Msg> {
.collect(),
..Default::default()
}
.into_node()
.render()
};
StyledField {
label: "Current project",
input: div![C!["project-name"], inner],
..Default::default()
}
.into_node()
.render()
}
fn project_select_option<'l>(project: &'l Project) -> StyledSelectChild<'l> {
StyledSelectChild {
fn project_select_option<'l>(project: &'l Project) -> StyledSelectOption<'l> {
StyledSelectOption {
text: Some(project.name.as_str()),
value: project.id as u32,
..Default::default()

View File

@ -2,9 +2,9 @@ use seed::prelude::*;
use seed::*;
use crate::components::styled_button::StyledButton;
use crate::components::styled_icon::Icon;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::model::Model;
use crate::shared::{inner_layout, ToNode};
use crate::shared::inner_layout;
use crate::Msg;
mod board;
@ -41,7 +41,11 @@ fn header(model: &Model) -> Node<Msg> {
if !model.show_extras {
return Node::Empty;
}
let button = StyledButton::secondary_with_text_and_icon("Repository", Icon::Github).into_node();
let button = StyledButton::secondary_with_text_and_icon(
"Repository",
StyledIcon::from(Icon::Github).render(),
)
.render();
div![
id!["projectBoardHeader"],
div![id!["boardName"], C!["headerChild"], "Kanban board"],

View File

@ -6,7 +6,6 @@ use crate::components::styled_avatar::*;
use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::*;
use crate::model::PageContent;
use crate::shared::ToNode;
use crate::{BoardPageChange, Model, Msg, Page, PageChanged};
pub fn project_board_lists(model: &Model) -> Node<Msg> {
@ -37,7 +36,7 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
let id = *id;
let edit_button = StyledButton {
variant: ButtonVariant::Empty,
icon: Some(Icon::EditAlt.into_node()),
icon: Some(StyledIcon::from(Icon::EditAlt).render()),
on_click: Some(mouse_ev("click", move |ev| {
ev.stop_propagation();
ev.prevent_default();
@ -49,10 +48,10 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
})),
..Default::default()
}
.into_node();
.render();
let delete_button = StyledButton {
variant: ButtonVariant::Empty,
icon: Some(Icon::DeleteAlt.into_node()),
icon: Some(StyledIcon::from(Icon::DeleteAlt).render()),
on_click: Some(mouse_ev("click", move |ev| {
ev.stop_propagation();
ev.prevent_default();
@ -64,7 +63,7 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
})),
..Default::default()
}
.into_node();
.render();
div![
C!["epicHeader"],
@ -138,7 +137,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
name: &user.name,
..StyledAvatar::default()
}
.into_node()
.render()
})
.collect();
@ -148,7 +147,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
color: Some(issue.issue_type.to_str()),
..Default::default()
}
.into_node();
.render();
let priority_icon = StyledIcon {
icon: issue.priority.into(),
@ -156,7 +155,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
color: Some(issue.priority.to_str()),
..Default::default()
}
.into_node();
.render();
let issue_id = issue.id;
let drag_started = drag_ev(Ev::DragStart, move |ev| {

View File

@ -6,7 +6,6 @@ use crate::components::styled_button::*;
use crate::components::styled_icon::*;
use crate::components::styled_input::*;
use crate::model::PageContent;
use crate::shared::ToNode;
use crate::{FieldId, Model, Msg};
pub fn project_board_filters(model: &Model) -> Node<Msg> {
@ -22,7 +21,7 @@ pub fn project_board_filters(model: &Model) -> Node<Msg> {
icon: Some(Icon::Search),
..Default::default()
}
.into_node();
.render();
let only_my = StyledButton {
variant: ButtonVariant::Empty,
@ -32,7 +31,7 @@ pub fn project_board_filters(model: &Model) -> Node<Msg> {
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleOnlyMy)),
..Default::default()
}
.into_node();
.render();
let recently_updated = StyledButton {
variant: ButtonVariant::Empty,
@ -41,7 +40,7 @@ pub fn project_board_filters(model: &Model) -> Node<Msg> {
on_click: Some(mouse_ev(Ev::Click, |_| Msg::ProjectToggleRecentlyUpdated)),
..Default::default()
}
.into_node();
.render();
let clear_all = if project_page.only_my_filter
|| project_page.recently_updated_filter
@ -89,7 +88,7 @@ pub fn avatars_filters(model: &Model) -> Node<Msg> {
user_index: idx,
..StyledAvatar::default()
}
.into_node();
.render();
div![
IF![active => C!["isActive"]],
C!["avatarIsActiveBorder"],

View File

@ -12,11 +12,11 @@ use crate::components::styled_form::StyledForm;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_input::{InputVariant, StyledInput};
use crate::components::styled_select::{SelectVariant, StyledSelect};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::components::styled_textarea::StyledTextarea;
use crate::model::{self, ModalType, Model, PageContent};
use crate::pages::project_settings_page::ProjectSettingsPage;
use crate::shared::{inner_layout, ToNode};
use crate::shared::inner_layout;
use crate::{FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange};
// use crate::shared::styled_rte::StyledRte;
@ -40,10 +40,10 @@ pub fn view(model: &model::Model) -> Node<Msg> {
// StyledRte::build(FieldId::ProjectSettings(ProjectFieldId::
// Description)) .state(&page.description_rte)
// .build()
// .into_node(),
// .render(),
// )
// .build()
// .into_node();
// .render();
let category_field = category_field(page);
@ -62,7 +62,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
text: Some("Save changes"),
..Default::default()
}
.into_node();
.render();
let form = StyledForm {
heading: "Project Details",
@ -83,7 +83,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
))
})),
}
.into_node();
.render();
let project_section = [div![C!["formContainer"], form]];
@ -100,7 +100,7 @@ fn time_tracking_select(page: &ProjectSettingsPage) -> Node<Msg> {
),
class_list: "timeTracking",
}
.into_node();
.render();
let time_tracking_type: TimeTracking = page.time_tracking.value.into();
StyledField {
input: time_tracking,
@ -111,7 +111,7 @@ fn time_tracking_select(page: &ProjectSettingsPage) -> Node<Msg> {
}),
..Default::default()
}
.into_node()
.render()
}
fn time_tracking_checkbox_option<'l>(
@ -151,14 +151,14 @@ fn name_field(page: &ProjectSettingsPage) -> Node<Msg> {
disable_auto_resize: true,
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Name",
input: name,
tip: Some(""),
..Default::default()
}
.into_node()
.render()
}
/// Build project url input with styled field wrapper
@ -171,14 +171,14 @@ fn url_field(page: &ProjectSettingsPage) -> Node<Msg> {
value: page.payload.url.as_deref().unwrap_or_default(),
..Default::default()
}
.into_node();
.render();
StyledField {
label: "Url",
input: url,
tip: Some(""),
..Default::default()
}
.into_node()
.render()
}
/// Build project description text area with styled field wrapper
@ -191,14 +191,14 @@ fn description_field(page: &ProjectSettingsPage) -> Node<Msg> {
mode: page.description_mode.clone(),
update_event: Ev::Change,
}
.into_node();
.render();
StyledField {
label: "Description",
tip: Some("Describe the project in as much detail as you'd like."),
input: description,
..Default::default()
}
.into_node()
.render()
}
/// Build project category dropdown with styled field wrapper
@ -219,18 +219,18 @@ fn category_field(page: &ProjectSettingsPage) -> Node<Msg> {
)],
..Default::default()
}
.into_node();
.render();
StyledField {
input: category,
label: "Project Category",
..Default::default()
}
.into_node()
.render()
}
#[inline(always)]
fn category_select_option<'l>(pc: ProjectCategory) -> StyledSelectChild<'l> {
StyledSelectChild {
fn category_select_option<'l>(pc: ProjectCategory) -> StyledSelectOption<'l> {
StyledSelectOption {
class_list: pc.to_str(),
text: Some(pc.to_str()),
value: pc.into(),
@ -266,7 +266,7 @@ fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
input: columns_section,
class_list: "columnsField",
}
.into_node()
.render()
}
#[inline]
@ -300,14 +300,14 @@ fn add_column(page: &ProjectSettingsPage, column_style: &str) -> Node<Msg> {
input_handlers: vec![blur],
..Default::default()
}
.into_node();
.render();
div![
C!["columnPreview"],
div![C!["columnName"], form![on_submit, input]]
]
} else {
let add_column = StyledIcon::from(Icon::Plus).into_node();
let add_column = StyledIcon::from(Icon::Plus).render();
div![
C!["columnPreview"],
attrs![At::Style => column_style],
@ -339,7 +339,7 @@ fn column_preview(
id: Some(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName)),
..Default::default()
}
.into_node();
.render();
div![C!["columnPreview"], div![C!["columnName"], input]]
} else {
@ -391,11 +391,11 @@ fn show_column_preview(
let delete = StyledButton {
variant: ButtonVariant::Primary,
class_list: "removeColumn",
icon: Some(Icon::Trash.into_node()),
icon: Some(StyledIcon::from(Icon::Trash).render()),
on_click: Some(on_delete),
..Default::default()
}
.into_node();
.render();
div![C!["removeColumn"], delete]
} else {
div![

View File

@ -9,7 +9,7 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_link::*;
use crate::model::{Model, PageContent};
use crate::pages::reports_page::model::ReportsPage;
use crate::shared::{inner_layout, ToNode};
use crate::shared::inner_layout;
use crate::{Msg, PageChanged, ReportsPageChange};
const SVG_MARGIN_X: u32 = 10;
@ -202,9 +202,9 @@ fn issue_list(page: &ReportsPage, project_name: &str, this_month_updated: &[&Iss
let day = date.format("%Y-%m-%d").to_string();
let type_icon = StyledIcon::from(Icon::from(issue_type.clone()))
.into_node();
.render();
let priority_icon = StyledIcon::from(Icon::from(priority.clone()))
.into_node();
.render();
let desc = Node::from_html(None,
description
.as_deref()
@ -212,12 +212,12 @@ fn issue_list(page: &ReportsPage, project_name: &str, this_month_updated: &[&Iss
);
let link = StyledLink {
children: vec![
Icon::Link.into_node(),
StyledIcon::from(Icon::Link).render(),
span![format!("{}-{}", project_name, id).as_str()]
],
class_list: "withIcon",
href: format!("/issues/{}", id).as_str(),
}.into_node();
}.render();
li![
C!["issue", selection_state.to_str()],

View File

@ -8,7 +8,7 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_input::StyledInput;
use crate::components::styled_link::StyledLink;
use crate::model::{self, PageContent};
use crate::shared::{outer_layout, ToNode};
use crate::shared::outer_layout;
use crate::validations::{is_email, is_token};
use crate::{FieldId, Msg, SignInFieldId};
@ -24,13 +24,13 @@ pub fn view(model: &model::Model) -> Node<Msg> {
id: Some(FieldId::SignIn(SignInFieldId::Username)),
..Default::default()
}
.into_node();
.render();
let username_field = StyledField {
label: "Username",
input: username,
..Default::default()
}
.into_node();
.render();
let email = StyledInput {
value: page.email.as_str(),
@ -38,13 +38,13 @@ pub fn view(model: &model::Model) -> Node<Msg> {
id: Some(FieldId::SignIn(SignInFieldId::Email)),
..Default::default()
}
.into_node();
.render();
let email_field = StyledField {
label: "E-Mail",
input: email,
..Default::default()
}
.into_node();
.render();
let submit = if page.login_success {
StyledButton {
@ -60,18 +60,18 @@ pub fn view(model: &model::Model) -> Node<Msg> {
..Default::default()
}
}
.into_node();
.render();
let register_link = StyledLink {
children: vec![span!["Register"]],
class_list: "signUpLink",
href: "/register",
}
.into_node();
.render();
let submit_field = StyledField {
input: div![C!["twoRow"], submit, register_link],
..Default::default()
}
.into_node();
.render();
let help_icon = StyledIcon {
icon: Icon::Help,
@ -79,7 +79,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
size: Some(22),
..Default::default()
}
.into_node();
.render();
let no_pass_section = div![
C!["noPasswordSection"],
@ -97,32 +97,32 @@ pub fn view(model: &model::Model) -> Node<Msg> {
Msg::SignInRequest
})),
}
.into_node();
.render();
let token = StyledInput::new_with_id_and_value_and_valid(
FieldId::SignIn(SignInFieldId::Token),
&page.token,
is_valid_token(page.token_touched, page.token.as_str()),
)
.into_node();
.render();
let token_field = StyledField {
label: "Single use token",
input: token,
..Default::default()
}
.into_node();
.render();
let submit_token = StyledButton {
variant: ButtonVariant::Primary,
text: Some("Authorize"),
on_click: Some(mouse_ev(Ev::Click, |_| Msg::BindClientRequest)),
..Default::default()
}
.into_node();
.render();
let submit_token_field = StyledField {
input: submit_token,
..Default::default()
}
.into_node();
.render();
let bind_token_form = StyledForm {
on_submit: Some(ev(Ev::Submit, |ev| {
@ -133,7 +133,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
fields: vec![token_field, submit_token_field],
..Default::default()
}
.into_node();
.render();
let children = vec![sign_in_form, bind_token_form];
outer_layout(model, "login", children)

View File

@ -9,7 +9,7 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_input::StyledInput;
use crate::components::styled_link::StyledLink;
use crate::model::{self, PageContent};
use crate::shared::{outer_layout, ToNode};
use crate::shared::outer_layout;
use crate::validations::is_email;
use crate::{match_page, FieldId, Msg};
@ -22,13 +22,13 @@ pub fn view(model: &model::Model) -> Node<Msg> {
id: Some(FieldId::SignUp(SignUpFieldId::Username)),
..Default::default()
}
.into_node();
.render();
let username_field = StyledField {
label: "Username",
input: username,
..Default::default()
}
.into_node();
.render();
let email = StyledInput {
value: page.email.as_str(),
@ -36,13 +36,13 @@ pub fn view(model: &model::Model) -> Node<Msg> {
id: Some(FieldId::SignUp(SignUpFieldId::Email)),
..Default::default()
}
.into_node();
.render();
let email_field = StyledField {
label: "E-Mail",
input: email,
..Default::default()
}
.into_node();
.render();
let submit = if page.sign_up_success {
StyledButton {
@ -58,20 +58,20 @@ pub fn view(model: &model::Model) -> Node<Msg> {
..Default::default()
}
}
.into_node();
.render();
let sign_in_link = StyledLink {
children: vec![span!["Sign In"]],
class_list: "signInLink",
href: "/login",
}
.into_node();
.render();
let submit_field = StyledField {
input: div![C!["twoRow"], submit, sign_in_link],
..Default::default()
}
.into_node();
.render();
let help_icon = StyledIcon {
icon: Icon::Help,
@ -79,7 +79,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
size: Some(22),
..Default::default()
}
.into_node();
.render();
let no_pass_section = div![
C!["noPasswordSection"],
@ -109,7 +109,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
error_row,
],
}
.into_node();
.render();
let children = vec![sign_up_form];
outer_layout(model, "register", children)
}

View File

@ -7,10 +7,10 @@ use crate::components::styled_field::StyledField;
use crate::components::styled_form::StyledForm;
use crate::components::styled_input::{InputVariant, StyledInput};
use crate::components::styled_select::{SelectVariant, StyledSelect};
use crate::components::styled_select_child::StyledSelectChild;
use crate::components::styled_select_child::StyledSelectOption;
use crate::model::{InvitationFormState, Model, PageContent};
use crate::pages::users_page::UsersPage;
use crate::shared::{inner_layout, ToNode};
use crate::shared::inner_layout;
use crate::validations::is_email;
use crate::{FieldId, Msg, PageChanged, UsersPageChange};
@ -27,13 +27,13 @@ pub fn view(model: &Model) -> Node<Msg> {
id: Some(FieldId::Users(UsersFieldId::Username)),
..Default::default()
}
.into_node();
.render();
let name_field = StyledField {
label: "Name",
input: name,
..Default::default()
}
.into_node();
.render();
let email = StyledInput {
id: Some(FieldId::Users(UsersFieldId::Email)),
@ -42,13 +42,13 @@ pub fn view(model: &Model) -> Node<Msg> {
variant: InputVariant::Normal,
..Default::default()
}
.into_node();
.render();
let email_field = StyledField {
input: email,
label: "E-Mail",
..Default::default()
}
.into_node();
.render();
let user_role_field = user_role_select(page);
@ -59,7 +59,7 @@ pub fn view(model: &Model) -> Node<Msg> {
active: page.form_state != InvitationFormState::Sent,
..Default::default()
}
.into_node();
.render();
let submit_supplement = match page.form_state {
InvitationFormState::Succeed => StyledButton {
variant: ButtonVariant::Empty,
@ -72,7 +72,7 @@ pub fn view(model: &Model) -> Node<Msg> {
button_type: "reset",
..Default::default()
}
.into_node(),
.render(),
InvitationFormState::Failed => div![C!["error"], "There was an error"],
_ => empty![],
};
@ -80,7 +80,7 @@ pub fn view(model: &Model) -> Node<Msg> {
input: div![C!["invitationActions"], submit, submit_supplement],
..Default::default()
}
.into_node();
.render();
let form = StyledForm {
heading: "Invite new user",
@ -90,7 +90,7 @@ pub fn view(model: &Model) -> Node<Msg> {
})),
fields: vec![name_field, email_field, user_role_field, submit_field],
}
.into_node();
.render();
let users: Vec<Node<Msg>> = page
.invited_users
@ -104,7 +104,7 @@ pub fn view(model: &Model) -> Node<Msg> {
})),
..Default::default()
}
.into_node();
.render();
let role = page
.invitations
.iter()
@ -139,13 +139,13 @@ pub fn view(model: &Model) -> Node<Msg> {
on_click: Some(mouse_ev(Ev::Click, move |_| Msg::InviteRevokeRequest(id))),
..Default::default()
}
.into_node();
.render();
// let revoke = StyledButton::build()
// .text("Revoke")
// .disabled(invitation.state == InvitationState::Revoked)
// .on_click(mouse_ev(Ev::Click, move |_| Msg::InviteRevokeRequest(id)))
// .build()
// .into_node();
// .render();
li![
C!["invitation", invitation.state.to_str()],
span![invitation.name.as_str()],
@ -178,19 +178,19 @@ fn user_role_select(page: &UsersPage) -> Node<Msg> {
options: Some(UserRole::default().into_iter().map(user_role_select_option)),
..Default::default()
}
.into_node();
.render();
StyledField {
input: user_role,
label: "Role",
..Default::default()
}
.into_node()
.render()
}
fn user_role_select_option<'l>(ur: UserRole) -> StyledSelectChild<'l> {
fn user_role_select_option<'l>(ur: UserRole) -> StyledSelectOption<'l> {
let name = ur.to_str();
StyledSelectChild {
StyledSelectOption {
text: Some(name),
value: ur.into(),
class_list: name,

View File

@ -4,7 +4,7 @@ use seed::*;
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::model::{Model, Page};
use crate::shared::{divider, ToNode};
use crate::shared::divider;
use crate::ws::enqueue_ws_msg;
use crate::{Msg, OperationKind, ResourceKind};
@ -96,7 +96,7 @@ fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>)
None
};
let active_flag = page.filter(|p| *p == model.page).map(|_| C!["active"]);
let icon_node = StyledIcon::from(icon).into_node();
let icon_node = StyledIcon::from(icon).render();
let on_click = page.map(|p| {
mouse_ev("click", move |ev| {
ev.stop_propagation();

View File

@ -42,10 +42,6 @@ pub fn go_to(url: &str, orders: &mut impl Orders<Msg>) {
}
}
pub trait ToNode {
fn into_node(self) -> Node<Msg>;
}
#[inline]
pub fn divider() -> Node<Msg> {
div![C!["divider"], ""]

View File

@ -8,7 +8,7 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_tooltip;
use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant};
use crate::model::Model;
use crate::shared::{divider, ToNode};
use crate::shared::divider;
use crate::ws::send_ws_msg;
use crate::{Msg, Page};
@ -29,7 +29,7 @@ impl IntoNavItemIcon for Icon {
size: Some(21),
..Default::default()
}
.into_node()
.render()
}
}
@ -56,7 +56,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
name: &user.name,
..StyledAvatar::default()
}
.into_node();
.render();
i![C!["styledIcon"], avatar]
}
_ => StyledIcon {
@ -64,7 +64,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
size: Some(21),
..Default::default()
}
.into_node(),
.render(),
};
let messages = if model.messages.is_empty() {
@ -177,7 +177,7 @@ fn messages_tooltip_popup(model: &Model) -> Node<Msg> {
children: vec![body],
variant: TooltipVariant::Messages,
}
.into_node()
.render()
}
fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
@ -194,7 +194,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
let hyperlink = if hyper_link.is_empty() && !hyper_link.starts_with('#') {
empty![]
} else {
let link_icon = StyledIcon::from(Icon::Link).into_node();
let link_icon = StyledIcon::from(Icon::Link).render();
div![
C!["hyperlink"],
a![
@ -209,7 +209,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
let message_description = parse_description(model, description.as_str());
let close_button = StyledButton {
variant: ButtonVariant::Empty,
icon: Some(Icon::Close.into_node()),
icon: Some(StyledIcon::from(Icon::Close).render()),
on_click: Some(mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
@ -217,7 +217,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
})),
..Default::default()
}
.into_node();
.render();
let top = div![
C!["top"],
div![C!["summary"], summary],
@ -231,7 +231,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
variant: ButtonVariant::Primary,
active: true,
text: Some("Accept"),
icon: Some(Icon::Check.into_node()),
icon: Some(StyledIcon::from(Icon::Check).render()),
on_click: Some(mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
@ -239,12 +239,12 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
})),
..Default::default()
}
.into_node();
.render();
let reject = StyledButton {
variant: ButtonVariant::Danger,
active: true,
text: Some("Dismiss"),
icon: Some(Icon::Close.into_node()),
icon: Some(StyledIcon::from(Icon::Close).render()),
on_click: Some(mouse_ev(Ev::Click, move |ev| {
ev.stop_propagation();
ev.prevent_default();
@ -252,7 +252,7 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
})),
..Default::default()
}
.into_node();
.render();
div![
C!["message"],
attrs![At::Class => format!("{}", message_type)],
@ -283,14 +283,14 @@ fn about_tooltip_popup(model: &Model) -> Node<Msg> {
text: Some("Visit Website"),
..Default::default()
}
.into_node();
.render();
let github_repo = StyledButton {
variant: ButtonVariant::Secondary,
text: Some("Github Repo"),
icon: Some(Icon::Github.into_node()),
icon: Some(StyledIcon::from(Icon::Github).render()),
..Default::default()
}
.into_node();
.render();
let on_click = mouse_ev(Ev::Click, |_| {
Msg::ToggleTooltip(styled_tooltip::TooltipVariant::About)
@ -339,7 +339,7 @@ fn about_tooltip_popup(model: &Model) -> Node<Msg> {
children: vec![body],
variant: TooltipVariant::About,
}
.into_node()
.render()
}
fn parse_description(model: &Model, desc: &str) -> Node<Msg> {
@ -360,7 +360,7 @@ fn parse_description(model: &Model, desc: &str) -> Node<Msg> {
user_index: index,
..StyledAvatar::default()
}
.into_node();
.render();
span![C!["mention"], avatar, user.name.as_str()]
})
.unwrap_or_else(|| span![word]);

View File

@ -6,7 +6,6 @@ use crate::components::styled_icon::{Icon, StyledIcon};
use crate::modals::issues_edit::Model as EditIssueModal;
use crate::modals::time_tracking::value_for_time_tracking;
use crate::model::{ModalType, Model};
use crate::shared::ToNode;
use crate::Msg;
#[inline]
@ -69,7 +68,7 @@ pub fn tracking_widget(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
size: Some(32),
..Default::default()
}
.into_node();
.render();
let bar_width = calc_bar_width(*estimate, *time_spent, *time_remaining);
let spent_text = match (time_spent, time_tracking_type) {