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)
}
#[inline(always)]
pub fn user_name(&self) -> Option<&str> {
self.user.as_ref().map(|u| u.name())
}
#[inline(always)]
pub fn project_id(&self) -> Option<ProjectId> {
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 {
self.current_user_project
.as_ref()

View File

@ -22,16 +22,11 @@ pub fn view(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![
C!["breadcrumbsContainer"],
span!["Projects"],
span![C!["breadcrumbsDivider"], "/"],
span![project_name],
span![model.project_name().unwrap_or_default()],
span![C!["breadcrumbsDivider"], "/"],
span!["Kanban Board"]
]
@ -41,17 +36,16 @@ fn header(model: &Model) -> Node<Msg> {
if !model.show_extras {
return Node::Empty;
}
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"],
a![
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_icon::*;
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> {
let project_page = match &model.page_content {
PageContent::Project(project_page) => project_page,
_ => return empty![],
};
let project_page = match_page!(model, Project; Empty);
let rows = project_page.visible_issues.iter().map(|per_epic| {
let columns: Vec<Node<Msg>> = per_epic
.per_status_issues
@ -78,6 +77,7 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
div![C!["rows"], rows]
}
#[inline(always)]
fn project_issue_list(
model: &Model,
status_id: IssueStatusId,
@ -86,7 +86,7 @@ fn project_issue_list(
) -> Node<Msg> {
let issues: Vec<Node<Msg>> = issues
.iter()
.map(|issue| project_issue(model, issue))
.map(|issue| ProjectIssue { model, issue }.render())
.collect();
let drop_handler = {
let send_status = status_id;
@ -121,100 +121,107 @@ fn project_issue_list(
]
}
fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
let is_dragging = match &model.page_content {
PageContent::Project(project_page) => project_page.issue_drag.is_dragging(),
_ => false,
};
let avatars: Vec<Node<Msg>> = issue
.user_ids
.iter()
.filter_map(|id| model.users_by_id.get(id))
.map(|user| {
StyledAvatar {
avatar_url: user.avatar_url.as_deref(),
size: 24,
name: &user.name,
..StyledAvatar::default()
}
.render()
})
.collect();
pub struct ProjectIssue<'l> {
pub model: &'l Model,
pub issue: &'l Issue,
}
let issue_type_icon = StyledIcon {
icon: issue.issue_type.into(),
class_list: issue.issue_type.to_str(),
color: Some(issue.issue_type.to_str()),
..Default::default()
}
.render();
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(),
_ => false,
};
let avatars: Vec<Node<Msg>> = self
.issue
.user_ids
.iter()
.filter_map(|id| self.model.users_by_id.get(id))
.map(|user| {
StyledAvatar {
avatar_url: user.avatar_url.as_deref(),
size: 24,
name: &user.name,
..StyledAvatar::default()
}
.render()
})
.collect();
let priority_icon = StyledIcon {
icon: issue.priority.into(),
class_list: issue.priority.to_str(),
color: Some(issue.priority.to_str()),
..Default::default()
}
.render();
let issue_type_icon = StyledIcon {
icon: self.issue.issue_type.into(),
class_list: self.issue.issue_type.to_str(),
color: Some(self.issue.issue_type.to_str()),
..Default::default()
}
.render();
let issue_id = issue.id;
let drag_started = drag_ev(Ev::DragStart, move |ev| {
ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::IssueDragStarted(issue_id),
)))
});
let drag_stopped = drag_ev(Ev::DragEnd, move |ev| {
ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::IssueDragStopped(issue_id),
)))
});
let drag_over_handler = drag_ev(Ev::DragEnter, move |ev| {
ev.prevent_default();
ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::ChangePosition(issue_id),
)))
});
let priority_icon = StyledIcon {
icon: self.issue.priority.into(),
class_list: self.issue.priority.to_str(),
color: Some(self.issue.priority.to_str()),
..Default::default()
}
.render();
let drag_out = drag_ev(Ev::DragLeave, move |_| {
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::DragLeave(issue_id),
)))
});
let on_click = mouse_ev("click", move |ev| {
ev.prevent_default();
ev.stop_propagation();
seed::Url::new()
.add_path_part("issues")
.add_path_part(format!("{}", issue_id))
.go_and_push();
Msg::ChangePage(Page::EditIssue(issue_id))
});
let issue_id = self.issue.id;
let drag_started = drag_ev(Ev::DragStart, move |ev| {
ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::IssueDragStarted(issue_id),
)))
});
let drag_stopped = drag_ev(Ev::DragEnd, move |ev| {
ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::IssueDragStopped(issue_id),
)))
});
let drag_over_handler = drag_ev(Ev::DragEnter, move |ev| {
ev.prevent_default();
ev.stop_propagation();
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::ChangePosition(issue_id),
)))
});
let href = format!("/issues/{id}", id = issue_id);
let drag_out = drag_ev(Ev::DragLeave, move |_| {
Some(Msg::PageChanged(PageChanged::Board(
BoardPageChange::DragLeave(issue_id),
)))
});
let on_click = mouse_ev("click", move |ev| {
ev.prevent_default();
ev.stop_propagation();
seed::Url::new()
.add_path_part("issues")
.add_path_part(format!("{}", issue_id))
.go_and_push();
Msg::ChangePage(Page::EditIssue(issue_id))
});
a![
drag_started,
on_click,
C!["issueLink"],
attrs![At::Href => href],
IF![is_dragging => div![C!["dragCover"], drag_over_handler]],
div![
C!["issue"],
attrs![At::Draggable => true],
drag_stopped,
drag_out,
p![C!["title"], issue.title.as_str()],
a![
drag_started,
on_click,
C!["issueLink"],
attrs![At::Href => format!("/issues/{id}", id = issue_id)],
IF![is_dragging => div![C!["dragCover"], drag_over_handler]],
div![
C!["bottom"],
C!["issue"],
attrs![At::Draggable => true],
drag_stopped,
drag_out,
p![C!["title"], self.issue.title.as_str()],
div![
div![C!["issueTypeIcon"], issue_type_icon],
div![C!["issuePriorityIcon"], priority_icon]
],
div![C!["assignees"], avatars,],
C!["bottom"],
div![
div![C!["issueTypeIcon"], issue_type_icon],
div![C!["issuePriorityIcon"], priority_icon]
],
div![C!["assignees"], avatars,],
]
]
]
]
}
}

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);
}
#[inline(always)]
pub fn render(model: &Model) -> Vec<Node<Msg>> {
let logo_svg = img![
attrs![At::Src => "/logo2.svg"; At::Style => "background: rgba(244,244,244,.8); border-radius: 24px;"]
];
let user_icon = match model.user.as_ref() {
Some(user) => {
let avatar = StyledAvatar {
avatar_url: user.avatar_url.as_deref(),
size: 27,
name: &user.name,
..StyledAvatar::default()
let user_icon = model.user.as_ref().map_or_else(
|| {
StyledIcon {
icon: Icon::User,
size: Some(21),
..Default::default()
}
.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)
})),
)
};
.render()
},
|user| {
i![
C!["styledIcon"],
StyledAvatar {
avatar_url: user.avatar_url.as_deref(),
size: 27,
name: &user.name,
..StyledAvatar::default()
}
.render()
]
},
);
let issue_nav = if model.issue_statuses.is_empty() {
vec![]
} else {
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(
"Create Issue",
Icon::Plus,
StyledIcon::from(Icon::Plus).render(),
Some("/add-issue"),
Some(mouse_ev("click", |ev| {
ev.stop_propagation();
@ -119,10 +115,18 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
div![
C!["bottom"],
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(
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]
fn navbar_left_item<I>(
fn navbar_left_item(
text: &str,
icon: I,
icon: Node<Msg>,
href: Option<&str>,
on_click: Option<EventHandler<Msg>>,
) -> Node<Msg>
where
I: IntoNavItemIcon,
{
) -> Node<Msg> {
let styled_icon = icon.into_nav_item_icon();
a![

View File

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