Optimizations

This commit is contained in:
eraden 2021-04-11 22:09:42 +02:00
parent 1c666dc26a
commit 82971adb48
20 changed files with 613 additions and 501 deletions

792
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@ default = []
[dependencies]
jirs-data = { path = "../shared/jirs-data", features = ["frontend"] }
seed = { version = "0.8.0" }
seed = { git = "https://github.com/seed-rs/seed.git" }
serde = { version = "*" }
serde_json = { version = "*" }

View File

@ -162,6 +162,26 @@ pub struct StyledButton<'l> {
button_id: Option<ButtonId>,
}
impl<'l> StyledButton<'l> {
pub fn secondary_with_text_and_icon<I>(text: &'l str, icon: I) -> Self
where
I: ToNode,
{
Self {
variant: Variant::Secondary,
disabled: false,
active: false,
text: Some(text),
icon: Some(icon.into_node()),
on_click: None,
children: vec![],
class_list: vec![],
button_type: "",
button_id: None,
}
}
}
impl<'l> StyledButton<'l> {
#[inline(always)]
pub fn build() -> StyledButtonBuilder<'l> {

View File

@ -169,7 +169,7 @@ pub fn render(values: StyledEditor) -> Node<Msg> {
C!["viewRadio"],
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
],
Node::from_html(html.as_str()),
Node::from_html(None, html.as_str()),
),
};

View File

@ -56,7 +56,7 @@ impl<'l> StyledFieldBuilder<'l> {
StyledField {
label: self.label.unwrap_or_default(),
tip: self.tip,
input: self.input.unwrap_or_else(|| empty![]),
input: self.input.unwrap_or(empty![]),
class_list: self.class_list,
}
}
@ -69,10 +69,7 @@ pub fn render(values: StyledField) -> Node<Msg> {
input,
class_list,
} = values;
let tip_node = match tip {
Some(s) => div![attrs![At::Class => "styledTip"], s],
_ => empty![],
};
let tip_node = tip.map(|s| div![C!["styledTip"], s]).unwrap_or(empty![]);
div![
attrs![At::Class => class_list.join(" "), At::Class => "styledField"],

View File

@ -113,6 +113,22 @@ pub struct StyledInput<'l, 'm: 'l> {
}
impl<'l, 'm: 'l> StyledInput<'l, 'm> {
#[inline]
pub fn new_with_id_and_value_and_valid(id: FieldId, value: &'m str, valid: bool) -> Self {
Self {
id,
icon: None,
valid,
value: Some(value),
input_type: None,
input_class_list: vec![],
wrapper_class_list: vec![],
variant: Variant::Normal,
auto_focus: false,
input_handlers: vec![],
}
}
#[inline]
pub fn build() -> StyledInputBuilder<'l, 'm> {
StyledInputBuilder {

View File

@ -39,6 +39,18 @@ pub struct StyledModal<'l> {
class_list: Vec<&'l str>,
}
impl<'l> StyledModal<'l> {
pub fn centered_with_width_and_body(width: usize, children: Vec<Node<Msg>>) -> Self {
Self {
variant: Variant::Center,
width: Some(width),
with_icon: false,
children,
class_list: vec![],
}
}
}
impl<'l> ToNode for StyledModal<'l> {
fn into_node(self) -> Node<Msg> {
render(self)

View File

@ -1,4 +1,4 @@
#![feature(or_patterns, type_ascription, trait_alias, drain_filter)]
#![feature(type_ascription, trait_alias, drain_filter)]
use {
crate::{

View File

@ -1,4 +1,4 @@
pub use {view::*, model::*};
pub use {model::*, view::*};
mod view;
mod model;
mod view;

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -20,18 +20,13 @@ use {
mod comments;
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
let issue_id = modal.id;
if let Some(_issue) = model.issues_by_id.get(&issue_id) {
let details = details(model, modal);
StyledModal::build()
.variant(crate::components::styled_modal::Variant::Center)
.width(1040)
.child(details)
.build()
.into_node()
} else {
Node::Empty
}
model
.issues_by_id
.get(&modal.id)
.map(|_issue| {
StyledModal::centered_with_width_and_body(1040, vec![details(model, modal)]).into_node()
})
.unwrap_or(Node::Empty)
}
pub fn details(model: &Model, modal: &EditIssueModal) -> Node<Msg> {

View File

@ -4,66 +4,56 @@ use {
};
pub fn view(model: &Model) -> Node<Msg> {
let mut nodes = Vec::with_capacity(model.modal_stack().len());
for modal_type in model.modal_stack() {
match modal_type {
ModalType::AddIssue(_) => {
if let Some(modal) = &model.modals().add_issue {
let node = crate::modals::issues_create::view(model, modal);
nodes.push(node);
}
}
ModalType::EditIssue(_) => {
if let Some(modal) = &model.modals().edit_issue {
let node = crate::modals::issues_edit::view(model, modal);
nodes.push(node);
}
}
ModalType::DeleteEpic(_) => {
if let Some(modal) = &model.modals().delete_epic {
let node = crate::modals::epics_delete::view(model, modal);
nodes.push(node);
}
}
ModalType::EditEpic(_) => {
if let Some(modal) = &model.modals().edit_epic {
let node = crate::modals::epics_edit::view(model, modal);
nodes.push(node);
}
}
ModalType::DeleteIssueConfirm(_) => {
if let Some(_issue_id) = &model.modals().delete_issue_confirm {
let node = crate::modals::issues_delete::view(model);
nodes.push(node);
}
}
ModalType::DeleteCommentConfirm(_) => {
if let Some(modal) = &model.modals().delete_comment_confirm {
let node = crate::modals::comments_delete::view(model, modal);
nodes.push(node);
}
}
ModalType::TimeTracking(_) => {
if let Some(modal) = &model.modals().time_tracking {
let node = crate::modals::time_tracking::view(model, modal);
nodes.push(node);
}
}
ModalType::DeleteIssueStatusModal(_) => {
if let Some(modal) = &model.modals().delete_issue_status_modal {
let node = crate::modals::issue_statuses_delete::view(model, modal.delete_id);
nodes.push(node);
}
}
let nodes = model
.modal_stack()
.iter()
.filter_map(|modal_type| match modal_type {
ModalType::AddIssue(_) => model
.modals()
.add_issue
.as_ref()
.map(|modal| crate::modals::issues_create::view(model, modal)),
ModalType::EditIssue(_) => model
.modals()
.edit_issue
.as_ref()
.map(|modal| crate::modals::issues_edit::view(model, modal)),
ModalType::DeleteEpic(_) => model
.modals()
.delete_epic
.as_ref()
.map(|modal| crate::modals::epics_delete::view(model, modal)),
ModalType::EditEpic(_) => model
.modals()
.edit_epic
.as_ref()
.map(|modal| crate::modals::epics_edit::view(model, modal)),
ModalType::DeleteIssueConfirm(_) => model
.modals()
.delete_issue_confirm
.as_ref()
.map(|_id| crate::modals::issues_delete::view(model)),
ModalType::DeleteCommentConfirm(_) => model
.modals()
.delete_comment_confirm
.as_ref()
.map(|modal| crate::modals::comments_delete::view(model, modal)),
ModalType::TimeTracking(_) => model
.modals()
.time_tracking
.as_ref()
.map(|modal| crate::modals::time_tracking::view(model, modal)),
ModalType::DeleteIssueStatusModal(_) => model
.modals()
.delete_issue_status_modal
.as_ref()
.map(|modal| crate::modals::issue_statuses_delete::view(model, modal.delete_id)),
#[cfg(debug_assertions)]
ModalType::DebugModal(_) => {
if let Some(true) = &model.modals().debug_modal {
let node = crate::modals::debug::view(model);
nodes.push(node)
}
}
};
}
ModalType::DebugModal(_) => model
.modals()
.debug_modal
.as_ref()
.map(|_| crate::modals::debug::view(model)),
});
section![id!["modals"], nodes]
}

View File

@ -1,10 +1,11 @@
use chrono::NaiveDateTime;
use {crate::shared::drag::DragState, jirs_data::*, std::collections::HashMap};
#[derive(Default, Debug)]
pub struct StatusIssueIds {
pub status_id: IssueStatusId,
pub status_name: IssueStatusName,
pub issue_ids: Vec<EpicId>,
pub issue_ids: Vec<IssueId>,
}
#[derive(Default, Debug)]
@ -31,57 +32,67 @@ impl ProjectPage {
issues: &[Issue],
user: &Option<User>,
) -> Vec<EpicIssuePerStatus> {
let mut map = vec![];
let epics = vec![None]
.into_iter()
.chain(epics.iter().map(|s| Some((s.id, s.name.as_str()))));
let statuses = statuses.iter().map(|s| (s.id, s.name.as_str()));
let mut issues: Vec<&Issue> = issues.iter().collect();
if page.recently_updated_filter {
let issues = issues.iter().filter(|issue| {
issue_filter_with_avatars(issue, &page.active_avatar_filters)
&& issue_filter_with_text(issue, page.text_filter.as_str())
&& issue_filter_with_only_my(issue, page.only_my_filter, user)
});
let issues = if page.recently_updated_filter {
let mut m = HashMap::new();
let mut sorted = vec![];
for issue in issues.into_iter() {
sorted.push((issue.id, issue.updated_at));
m.insert(issue.id, issue);
}
let mut sorted: Vec<(IssueId, NaiveDateTime)> = issues
.map(|issue| {
m.insert(issue.id, issue);
(issue.id, issue.updated_at)
})
.collect();
sorted.sort_by(|(_, a_time), (_, b_time)| a_time.cmp(b_time));
issues = sorted
let mut issues: Vec<&Issue> = sorted
.into_iter()
.take(10)
.flat_map(|(id, _)| m.remove(&id))
.collect();
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
}
issues
} else {
issues.collect()
};
for epic in epics {
let mut per_epic_map = EpicIssuePerStatus {
epic_ref: epic.map(|(id, name)| (id, name.to_string())),
..Default::default()
};
let issues_per_epic_id = issues.into_iter().fold(HashMap::new(), |mut m, issue| {
m.entry(issue.epic_id).or_insert_with(Vec::new).push(issue);
m
});
for (current_status_id, issue_status_name) in statuses.to_owned() {
let mut per_status_map = StatusIssueIds {
status_id: current_status_id,
status_name: issue_status_name.to_string(),
epics
.map(|epic| {
let mut per_epic_map = EpicIssuePerStatus {
epic_ref: epic.map(|(id, name)| (id, name.to_string())),
..Default::default()
};
for issue in issues.iter() {
if issue.epic_id == epic.map(|(id, _)| id)
&& issue_filter_status(issue, current_status_id)
&& issue_filter_with_avatars(issue, &page.active_avatar_filters)
&& issue_filter_with_text(issue, page.text_filter.as_str())
&& issue_filter_with_only_my(issue, page.only_my_filter, user)
{
per_status_map.issue_ids.push(issue.id);
}
for (current_status_id, issue_status_name) in statuses.to_owned() {
let per_status_map = StatusIssueIds {
status_id: current_status_id,
status_name: issue_status_name.to_string(),
issue_ids: issues_per_epic_id
.get(&epic.map(|(id, _)| id))
.map(|v| {
v.iter()
.filter(|issue| issue_filter_status(issue, current_status_id))
.map(|issue| issue.id)
.collect()
})
.unwrap_or_default(),
};
per_epic_map.per_status_issues.push(per_status_map);
}
per_epic_map.per_status_issues.push(per_status_map);
}
map.push(per_epic_map);
}
map
per_epic_map
})
.collect()
}
}

View File

@ -42,12 +42,7 @@ fn header(model: &Model) -> Node<Msg> {
if !model.show_extras {
return Node::Empty;
}
let button = StyledButton::build()
.secondary()
.text("Repository")
.icon(Icon::Github)
.build()
.into_node();
let button = StyledButton::secondary_with_text_and_icon("Repository", Icon::Github).into_node();
div![
id!["projectBoardHeader"],
div![id!["boardName"], C!["headerChild"], "Kanban board"],

View File

@ -206,7 +206,7 @@ fn issue_list(page: &ReportsPage, project_name: &str, this_month_updated: &[&Iss
let priority_icon = StyledIcon::build(priority.clone().into())
.build()
.into_node();
let desc = Node::from_html(
let desc = Node::from_html(None,
description
.as_deref()
.unwrap_or_default()

View File

@ -24,7 +24,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let username = StyledInput::build()
.value(page.username.as_str())
.valid(!page.username_touched || page.username.len() > 1)
.valid(is_valid_username(page.username_touched, &page.username))
.build(FieldId::SignIn(SignInFieldId::Username))
.into_node();
let username_field = StyledField::build()
@ -35,7 +35,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let email = StyledInput::build()
.value(page.email.as_str())
.valid(!page.email_touched || is_email(page.email.as_str()))
.valid(is_valid_email(page.email_touched, page.email.as_str()))
.build(FieldId::SignIn(SignInFieldId::Email))
.into_node();
let email_field = StyledField::build()
@ -94,11 +94,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
.build()
.into_node();
let token = StyledInput::build()
.value(page.token.as_str())
.valid(!page.token_touched || is_token(page.token.as_str()))
.build(FieldId::SignIn(SignInFieldId::Token))
.into_node();
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();
let token_field = StyledField::build()
.label("Single use token")
.input(token)
@ -126,3 +127,15 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let children = vec![sign_in_form, bind_token_form];
outer_layout(model, "login", children)
}
fn is_valid_username(touched: bool, s: &str) -> bool {
!touched || (s.len() > 1 && s.len() < 20)
}
fn is_valid_email(touched: bool, s: &str) -> bool {
!touched || (is_email(s) && s.len() < 20)
}
fn is_valid_token(touched: bool, s: &str) -> bool {
!touched || is_token(s)
}

View File

@ -1,5 +1,4 @@
#![feature(async_closure)]
#![feature(vec_remove_item)]
#![recursion_limit = "256"]
use {

View File

@ -1,6 +1,9 @@
use {
serde::{de::DeserializeOwned, export::PhantomData, Serialize},
std::fs::{read_to_string, write},
serde::{de::DeserializeOwned, Serialize},
std::{
fs::{read_to_string, write},
marker::PhantomData,
},
};
pub struct Reader<T: DeserializeOwned + Default + Serialize> {