Small refactor

This commit is contained in:
Adrian Woźniak 2021-04-19 22:31:18 +02:00
parent 1117e878a8
commit e5280618aa
5 changed files with 169 additions and 145 deletions

View File

@ -307,11 +307,21 @@ impl Model {
self.user.as_ref().map(|u| u.id) self.user.as_ref().map(|u| u.id)
} }
#[inline(always)]
pub fn user_name(&self) -> Option<&str> {
self.user.as_ref().map(|u| u.name())
}
#[inline(always)] #[inline(always)]
pub fn project_id(&self) -> Option<ProjectId> { pub fn project_id(&self) -> Option<ProjectId> {
self.project.as_ref().map(|p| p.id) self.project.as_ref().map(|p| p.id)
} }
#[inline(always)]
pub fn project_name(&self) -> Option<&str> {
self.project.as_ref().map(|u| u.name())
}
pub fn current_user_role(&self) -> UserRole { pub fn current_user_role(&self) -> UserRole {
self.current_user_project self.current_user_project
.as_ref() .as_ref()

View File

@ -22,16 +22,11 @@ pub fn view(model: &Model) -> Node<Msg> {
} }
fn breadcrumbs(model: &Model) -> Node<Msg> { fn breadcrumbs(model: &Model) -> Node<Msg> {
let project_name = model
.project
.as_ref()
.map(|p| p.name.as_str())
.unwrap_or_default();
div![ div![
C!["breadcrumbsContainer"], C!["breadcrumbsContainer"],
span!["Projects"], span!["Projects"],
span![C!["breadcrumbsDivider"], "/"], span![C!["breadcrumbsDivider"], "/"],
span![project_name], span![model.project_name().unwrap_or_default()],
span![C!["breadcrumbsDivider"], "/"], span![C!["breadcrumbsDivider"], "/"],
span!["Kanban Board"] span!["Kanban Board"]
] ]
@ -41,17 +36,16 @@ fn header(model: &Model) -> Node<Msg> {
if !model.show_extras { if !model.show_extras {
return Node::Empty; return Node::Empty;
} }
let button = StyledButton::secondary_with_text_and_icon(
"Repository",
StyledIcon::from(Icon::Github).render(),
)
.render();
div![ div![
id!["projectBoardHeader"], id!["projectBoardHeader"],
div![id!["boardName"], C!["headerChild"], "Kanban board"], div![id!["boardName"], C!["headerChild"], "Kanban board"],
a![ a![
attrs![At::Href => "https://gitlab.com/adrian.wozniak/jirs", At::Target => "__blank", At::Rel => "noreferrer noopener"], attrs![At::Href => "https://gitlab.com/adrian.wozniak/jirs", At::Target => "__blank", At::Rel => "noreferrer noopener"],
button StyledButton::secondary_with_text_and_icon(
"Repository",
StyledIcon::from(Icon::Github).render(),
)
.render()
] ]
] ]
} }

View File

@ -6,13 +6,12 @@ use crate::components::styled_avatar::*;
use crate::components::styled_button::{ButtonVariant, StyledButton}; use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::*; use crate::components::styled_icon::*;
use crate::model::PageContent; use crate::model::PageContent;
use crate::{BoardPageChange, Model, Msg, Page, PageChanged}; use crate::{match_page, BoardPageChange, Model, Msg, Page, PageChanged};
#[inline(always)]
pub fn project_board_lists(model: &Model) -> Node<Msg> { pub fn project_board_lists(model: &Model) -> Node<Msg> {
let project_page = match &model.page_content { let project_page = match_page!(model, Project; Empty);
PageContent::Project(project_page) => project_page,
_ => return empty![],
};
let rows = project_page.visible_issues.iter().map(|per_epic| { let rows = project_page.visible_issues.iter().map(|per_epic| {
let columns: Vec<Node<Msg>> = per_epic let columns: Vec<Node<Msg>> = per_epic
.per_status_issues .per_status_issues
@ -78,6 +77,7 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
div![C!["rows"], rows] div![C!["rows"], rows]
} }
#[inline(always)]
fn project_issue_list( fn project_issue_list(
model: &Model, model: &Model,
status_id: IssueStatusId, status_id: IssueStatusId,
@ -86,7 +86,7 @@ fn project_issue_list(
) -> Node<Msg> { ) -> Node<Msg> {
let issues: Vec<Node<Msg>> = issues let issues: Vec<Node<Msg>> = issues
.iter() .iter()
.map(|issue| project_issue(model, issue)) .map(|issue| ProjectIssue { model, issue }.render())
.collect(); .collect();
let drop_handler = { let drop_handler = {
let send_status = status_id; let send_status = status_id;
@ -121,15 +121,23 @@ fn project_issue_list(
] ]
} }
fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> { pub struct ProjectIssue<'l> {
let is_dragging = match &model.page_content { pub model: &'l Model,
pub issue: &'l Issue,
}
impl<'l> ProjectIssue<'l> {
#[inline(always)]
pub fn render(self) -> Node<Msg> {
let is_dragging = match &self.model.page_content {
PageContent::Project(project_page) => project_page.issue_drag.is_dragging(), PageContent::Project(project_page) => project_page.issue_drag.is_dragging(),
_ => false, _ => false,
}; };
let avatars: Vec<Node<Msg>> = issue let avatars: Vec<Node<Msg>> = self
.issue
.user_ids .user_ids
.iter() .iter()
.filter_map(|id| model.users_by_id.get(id)) .filter_map(|id| self.model.users_by_id.get(id))
.map(|user| { .map(|user| {
StyledAvatar { StyledAvatar {
avatar_url: user.avatar_url.as_deref(), avatar_url: user.avatar_url.as_deref(),
@ -142,22 +150,22 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
.collect(); .collect();
let issue_type_icon = StyledIcon { let issue_type_icon = StyledIcon {
icon: issue.issue_type.into(), icon: self.issue.issue_type.into(),
class_list: issue.issue_type.to_str(), class_list: self.issue.issue_type.to_str(),
color: Some(issue.issue_type.to_str()), color: Some(self.issue.issue_type.to_str()),
..Default::default() ..Default::default()
} }
.render(); .render();
let priority_icon = StyledIcon { let priority_icon = StyledIcon {
icon: issue.priority.into(), icon: self.issue.priority.into(),
class_list: issue.priority.to_str(), class_list: self.issue.priority.to_str(),
color: Some(issue.priority.to_str()), color: Some(self.issue.priority.to_str()),
..Default::default() ..Default::default()
} }
.render(); .render();
let issue_id = issue.id; let issue_id = self.issue.id;
let drag_started = drag_ev(Ev::DragStart, move |ev| { let drag_started = drag_ev(Ev::DragStart, move |ev| {
ev.stop_propagation(); ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board( Some(Msg::PageChanged(PageChanged::Board(
@ -193,20 +201,18 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
Msg::ChangePage(Page::EditIssue(issue_id)) Msg::ChangePage(Page::EditIssue(issue_id))
}); });
let href = format!("/issues/{id}", id = issue_id);
a![ a![
drag_started, drag_started,
on_click, on_click,
C!["issueLink"], C!["issueLink"],
attrs![At::Href => href], attrs![At::Href => format!("/issues/{id}", id = issue_id)],
IF![is_dragging => div![C!["dragCover"], drag_over_handler]], IF![is_dragging => div![C!["dragCover"], drag_over_handler]],
div![ div![
C!["issue"], C!["issue"],
attrs![At::Draggable => true], attrs![At::Draggable => true],
drag_stopped, drag_stopped,
drag_out, drag_out,
p![C!["title"], issue.title.as_str()], p![C!["title"], self.issue.title.as_str()],
div![ div![
C!["bottom"], C!["bottom"],
div![ div![
@ -218,3 +224,4 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
] ]
] ]
} }
}

View File

@ -43,52 +43,48 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
send_ws_msg(m, model.ws.as_ref(), orders); send_ws_msg(m, model.ws.as_ref(), orders);
} }
#[inline(always)]
pub fn render(model: &Model) -> Vec<Node<Msg>> { pub fn render(model: &Model) -> Vec<Node<Msg>> {
let logo_svg = img![ let logo_svg = img![
attrs![At::Src => "/logo2.svg"; At::Style => "background: rgba(244,244,244,.8); border-radius: 24px;"] attrs![At::Src => "/logo2.svg"; At::Style => "background: rgba(244,244,244,.8); border-radius: 24px;"]
]; ];
let user_icon = match model.user.as_ref() { let user_icon = model.user.as_ref().map_or_else(
Some(user) => { || {
let avatar = StyledAvatar { StyledIcon {
icon: Icon::User,
size: Some(21),
..Default::default()
}
.render()
},
|user| {
i![
C!["styledIcon"],
StyledAvatar {
avatar_url: user.avatar_url.as_deref(), avatar_url: user.avatar_url.as_deref(),
size: 27, size: 27,
name: &user.name, name: &user.name,
..StyledAvatar::default() ..StyledAvatar::default()
} }
.render(); .render()
i![C!["styledIcon"], avatar] ]
} },
_ => StyledIcon { );
icon: Icon::User,
size: Some(21),
..Default::default()
}
.render(),
};
let messages = if model.messages.is_empty() {
empty![]
} else {
navbar_left_item(
"Messages",
Icon::Message,
None,
Some(mouse_ev(Ev::Click, |ev| {
ev.prevent_default();
Msg::ToggleTooltip(styled_tooltip::TooltipVariant::Messages)
})),
)
};
let issue_nav = if model.issue_statuses.is_empty() { let issue_nav = if model.issue_statuses.is_empty() {
vec![] vec![]
} else { } else {
vec![ vec![
navbar_left_item("Search issues", Icon::Search, None, None), navbar_left_item(
"Search issues",
StyledIcon::from(Icon::Search).render(),
None,
None,
),
navbar_left_item( navbar_left_item(
"Create Issue", "Create Issue",
Icon::Plus, StyledIcon::from(Icon::Plus).render(),
Some("/add-issue"), Some("/add-issue"),
Some(mouse_ev("click", |ev| { Some(mouse_ev("click", |ev| {
ev.stop_propagation(); ev.stop_propagation();
@ -119,10 +115,18 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
div![ div![
C!["bottom"], C!["bottom"],
navbar_left_item("Profile", user_icon, Some("/profile"), Some(go_to_profile)), navbar_left_item("Profile", user_icon, Some("/profile"), Some(go_to_profile)),
messages, IF![!model.messages.is_empty() => navbar_left_item(
"Messages",
StyledIcon::from(Icon::Message).render(),
None,
Some(mouse_ev(Ev::Click, |ev| {
ev.prevent_default();
Msg::ToggleTooltip(styled_tooltip::TooltipVariant::Messages)
})),
)],
IF![model.show_extras => about_tooltip( IF![model.show_extras => about_tooltip(
model, model,
navbar_left_item("About", Icon::Help, None, None) navbar_left_item("About", StyledIcon::from(Icon::Help).render(), None, None)
)], )],
], ],
], ],
@ -130,15 +134,12 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
} }
#[inline] #[inline]
fn navbar_left_item<I>( fn navbar_left_item(
text: &str, text: &str,
icon: I, icon: Node<Msg>,
href: Option<&str>, href: Option<&str>,
on_click: Option<EventHandler<Msg>>, on_click: Option<EventHandler<Msg>>,
) -> Node<Msg> ) -> Node<Msg> {
where
I: IntoNavItemIcon,
{
let styled_icon = icon.into_nav_item_icon(); let styled_icon = icon.into_nav_item_icon();
a![ a![

View File

@ -221,6 +221,12 @@ pub struct Project {
pub time_tracking: TimeTracking, pub time_tracking: TimeTracking,
} }
impl Project {
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Issue { pub struct Issue {
pub id: EpicId, pub id: EpicId,
@ -291,6 +297,12 @@ pub struct User {
pub updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
} }
impl User {
pub fn name(&self) -> &str {
&self.name
}
}
#[cfg_attr(feature = "backend", derive(Queryable))] #[cfg_attr(feature = "backend", derive(Queryable))]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct UserProject { pub struct UserProject {