Reduce model memory usage
This commit is contained in:
parent
5abcfb99a6
commit
d49c92cd53
@ -258,7 +258,7 @@ pub struct Issue {
|
|||||||
pub struct IssueStatus {
|
pub struct IssueStatus {
|
||||||
pub id: IssueStatusId,
|
pub id: IssueStatusId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub position: ProjectId,
|
pub position: ListPosition,
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
@ -11,7 +11,7 @@ pub fn epic_field<Modal>(model: &Model, modal: &Modal, field_id: FieldId) -> Opt
|
|||||||
where
|
where
|
||||||
Modal: IssueModal,
|
Modal: IssueModal,
|
||||||
{
|
{
|
||||||
if model.epics.is_empty() {
|
if model.epic_ids.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let input = StyledSelect {
|
let input = StyledSelect {
|
||||||
@ -19,10 +19,16 @@ where
|
|||||||
name: "epic",
|
name: "epic",
|
||||||
selected: vec![modal
|
selected: vec![modal
|
||||||
.epic_id_value()
|
.epic_id_value()
|
||||||
.and_then(|id| model.epics.iter().find(|epic| epic.id == id as EpicId))
|
.and_then(|id| model.epics_by_id.get(&(id as EpicId)))
|
||||||
.map(epic_select_option)
|
.map(epic_select_option)
|
||||||
.unwrap_or_default()],
|
.unwrap_or_default()],
|
||||||
options: Some(model.epics.iter().map(epic_select_option)),
|
options: Some(
|
||||||
|
model
|
||||||
|
.epic_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| model.epics_by_id.get(id))
|
||||||
|
.map(epic_select_option),
|
||||||
|
),
|
||||||
variant: SelectVariant::Normal,
|
variant: SelectVariant::Normal,
|
||||||
clearable: true,
|
clearable: true,
|
||||||
text_filter: modal.epic_state().text_filter.as_str(),
|
text_filter: modal.epic_state().text_filter.as_str(),
|
||||||
|
@ -339,15 +339,13 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
||||||
comment_id,
|
comment_id,
|
||||||
)) => {
|
)) => {
|
||||||
let id = *comment_id;
|
|
||||||
let body = model
|
let body = model
|
||||||
.comments
|
.comments_by_id
|
||||||
.iter()
|
.get(comment_id)
|
||||||
.find(|c| c.id == id)
|
|
||||||
.map(|c| c.body.clone())
|
.map(|c| c.body.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
modal.comment_form.body = body;
|
modal.comment_form.body = body;
|
||||||
modal.comment_form.id = Some(id);
|
modal.comment_form.id = Some(*comment_id);
|
||||||
modal.comment_form.creating = true;
|
modal.comment_form.creating = true;
|
||||||
}
|
}
|
||||||
Msg::DeleteComment(comment_id) => {
|
Msg::DeleteComment(comment_id) => {
|
||||||
|
@ -243,7 +243,11 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
vec![div![C!["fakeTextArea"], "Add a comment...", handler]]
|
vec![div![C!["fakeTextArea"], "Add a comment...", handler]]
|
||||||
};
|
};
|
||||||
|
|
||||||
let comments = model.comments.iter().flat_map(|c| comment(model, modal, c));
|
let comments = model
|
||||||
|
.comment_ids
|
||||||
|
.iter()
|
||||||
|
.flat_map(|id| model.comments_by_id.get(id))
|
||||||
|
.flat_map(|c| comment(model, modal, c));
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["left"],
|
C!["left"],
|
||||||
@ -403,11 +407,22 @@ fn status_select(
|
|||||||
opened: status_state.opened,
|
opened: status_state.opened,
|
||||||
variant: SelectVariant::Normal,
|
variant: SelectVariant::Normal,
|
||||||
text_filter: status_state.text_filter.as_str(),
|
text_filter: status_state.text_filter.as_str(),
|
||||||
options: Some(model.issue_statuses.iter().map(issue_status_select_option)),
|
options: Some(
|
||||||
selected: model
|
model
|
||||||
.issue_statuses
|
.issue_status_ids
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter_map(|id| model.issue_statuses_by_id.get(id))
|
||||||
|
.map(issue_status_select_option),
|
||||||
|
),
|
||||||
|
selected: model
|
||||||
|
.issue_status_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| {
|
||||||
|
model
|
||||||
|
.issue_statuses_by_id
|
||||||
|
.get(id)
|
||||||
.filter(|is| is.id == payload.issue_status_id)
|
.filter(|is| is.id == payload.issue_status_id)
|
||||||
|
})
|
||||||
.map(issue_status_select_option)
|
.map(issue_status_select_option)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
@ -424,7 +439,7 @@ fn status_select(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn issue_status_select_option<'l>(is: &'l IssueStatus) -> StyledSelectOption<'l> {
|
fn issue_status_select_option(is: &IssueStatus) -> StyledSelectOption<'_> {
|
||||||
StyledSelectOption {
|
StyledSelectOption {
|
||||||
value: is.id as u32,
|
value: is.id as u32,
|
||||||
class_list: is.name.as_str(),
|
class_list: is.name.as_str(),
|
||||||
|
@ -254,11 +254,11 @@ pub struct Model {
|
|||||||
pub user_settings: Option<UserSetting>,
|
pub user_settings: Option<UserSetting>,
|
||||||
|
|
||||||
// comments
|
// comments
|
||||||
pub comments: Vec<Comment>,
|
pub comment_ids: Vec<CommentId>,
|
||||||
pub comments_by_id: HashMap<CommentId, Comment>,
|
pub comments_by_id: HashMap<CommentId, Comment>,
|
||||||
|
|
||||||
// issue_statuses
|
// issue_statuses
|
||||||
pub issue_statuses: Vec<IssueStatus>,
|
pub issue_status_ids: Vec<IssueStatusId>,
|
||||||
pub issue_statuses_by_id: HashMap<IssueStatusId, IssueStatus>,
|
pub issue_statuses_by_id: HashMap<IssueStatusId, IssueStatus>,
|
||||||
pub issue_statuses_by_name: HashMap<String, IssueStatus>,
|
pub issue_statuses_by_name: HashMap<String, IssueStatus>,
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ pub struct Model {
|
|||||||
pub projects: Vec<Project>,
|
pub projects: Vec<Project>,
|
||||||
|
|
||||||
// epics
|
// epics
|
||||||
pub epics: Vec<Epic>,
|
pub epic_ids: Vec<EpicId>,
|
||||||
pub epics_by_id: HashMap<EpicId, Epic>,
|
pub epics_by_id: HashMap<EpicId, Epic>,
|
||||||
|
|
||||||
pub key_triggers: std::rc::Rc<std::cell::RefCell<HashMap<char, Box<dyn BuildMsg>>>>,
|
pub key_triggers: std::rc::Rc<std::cell::RefCell<HashMap<char, Box<dyn BuildMsg>>>>,
|
||||||
@ -304,15 +304,15 @@ impl Model {
|
|||||||
user_ids: vec![],
|
user_ids: vec![],
|
||||||
users_by_id: HashMap::with_capacity(1_000),
|
users_by_id: HashMap::with_capacity(1_000),
|
||||||
user_settings: None,
|
user_settings: None,
|
||||||
comments: vec![],
|
comment_ids: vec![],
|
||||||
comments_by_id: HashMap::with_capacity(1_000),
|
comments_by_id: HashMap::with_capacity(1_000),
|
||||||
issue_statuses: vec![],
|
issue_status_ids: vec![],
|
||||||
issue_statuses_by_id: HashMap::with_capacity(1_000),
|
issue_statuses_by_id: HashMap::with_capacity(1_000),
|
||||||
issue_statuses_by_name: HashMap::with_capacity(1_000),
|
issue_statuses_by_name: HashMap::with_capacity(1_000),
|
||||||
messages: vec![],
|
messages: vec![],
|
||||||
user_projects: vec![],
|
user_projects: vec![],
|
||||||
projects: vec![],
|
projects: vec![],
|
||||||
epics: vec![],
|
epic_ids: vec![],
|
||||||
issues_by_id: HashMap::with_capacity(1_000),
|
issues_by_id: HashMap::with_capacity(1_000),
|
||||||
show_extras: false,
|
show_extras: false,
|
||||||
epics_by_id: HashMap::with_capacity(1_000),
|
epics_by_id: HashMap::with_capacity(1_000),
|
||||||
@ -323,16 +323,6 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn issue_statuses(&self) -> &[IssueStatus] {
|
|
||||||
&self.issue_statuses
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn epics(&self) -> &[Epic] {
|
|
||||||
&self.epics
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn user(&self) -> &Option<User> {
|
pub fn user(&self) -> &Option<User> {
|
||||||
&self.user
|
&self.user
|
||||||
|
@ -11,7 +11,11 @@ use crate::Msg;
|
|||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
let page = crate::match_page!(model, Epics; Empty);
|
let page = crate::match_page!(model, Epics; Empty);
|
||||||
|
|
||||||
let epics = model.epics.iter().map(|epic| {
|
let epics = model
|
||||||
|
.epic_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| model.epics_by_id.get(id))
|
||||||
|
.map(|epic| {
|
||||||
let issues = page.issues(epic.id).map(|v| {
|
let issues = page.issues(epic.id).map(|v| {
|
||||||
v.iter()
|
v.iter()
|
||||||
.filter_map(|i| model.issues_by_id.get(i))
|
.filter_map(|i| model.issues_by_id.get(i))
|
||||||
|
@ -29,24 +29,26 @@ pub struct ProjectPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectPage {
|
impl ProjectPage {
|
||||||
pub fn visible_issues<'issue, IssueStream>(
|
pub fn visible_issues<'model, IssueStream, IssueStatusStream, EpicStream>(
|
||||||
page: &ProjectPage,
|
page: &ProjectPage,
|
||||||
epics: &[Epic],
|
num_of_epics: usize,
|
||||||
statuses: &[IssueStatus],
|
epics: EpicStream,
|
||||||
|
statuses: IssueStatusStream,
|
||||||
issues: IssueStream,
|
issues: IssueStream,
|
||||||
user: &Option<User>,
|
user: &Option<User>,
|
||||||
) -> Vec<EpicIssuePerStatus>
|
) -> Vec<EpicIssuePerStatus>
|
||||||
where
|
where
|
||||||
IssueStream: std::iter::Iterator<Item = &'issue Issue>,
|
IssueStream: std::iter::Iterator<Item = &'model Issue>,
|
||||||
|
IssueStatusStream: std::iter::Iterator<Item = &'model IssueStatus>,
|
||||||
|
EpicStream: std::iter::Iterator<Item = &'model Epic>,
|
||||||
{
|
{
|
||||||
let num_of_epics = epics.len();
|
|
||||||
let epics = vec![None].into_iter().chain(
|
let epics = vec![None].into_iter().chain(
|
||||||
epics
|
epics.map(|epic| Some((epic.id, epic.name.as_str(), epic.starts_at, epic.ends_at))),
|
||||||
.iter()
|
|
||||||
.map(|epic| Some((epic.id, epic.name.as_str(), epic.starts_at, epic.ends_at))),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let statuses = statuses.iter().map(|s| (s.id, s.name.as_str()));
|
let statuses = statuses
|
||||||
|
.map(|s| (s.id, s.name.as_str()))
|
||||||
|
.collect::<Vec<(IssueStatusId, &str)>>();
|
||||||
let issues = issues.filter(|issue| {
|
let issues = issues.filter(|issue| {
|
||||||
issue_filter_with_avatars(issue, &page.active_avatar_filters)
|
issue_filter_with_avatars(issue, &page.active_avatar_filters)
|
||||||
&& issue_filter_with_text(issue, page.text_filter.as_str())
|
&& issue_filter_with_text(issue, page.text_filter.as_str())
|
||||||
@ -93,15 +95,15 @@ impl ProjectPage {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
for (current_status_id, issue_status_name) in statuses.to_owned() {
|
for (current_status_id, issue_status_name) in statuses.iter() {
|
||||||
let per_status_map = StatusIssueIds {
|
let per_status_map = StatusIssueIds {
|
||||||
status_id: current_status_id,
|
status_id: *current_status_id,
|
||||||
status_name: issue_status_name.to_string(),
|
status_name: issue_status_name.to_string(),
|
||||||
issue_ids: issues_per_epic_id
|
issue_ids: issues_per_epic_id
|
||||||
.get(&epic.map(|(id, ..)| id))
|
.get(&epic.map(|(id, ..)| id))
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
v.iter()
|
v.iter()
|
||||||
.filter(|issue| issue_filter_status(issue, current_status_id))
|
.filter(|issue| issue_filter_status(issue, *current_status_id))
|
||||||
.map(|issue| issue.id)
|
.map(|issue| issue.id)
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,7 @@ use seed::prelude::Orders;
|
|||||||
use crate::components::styled_select::StyledSelectChanged;
|
use crate::components::styled_select::StyledSelectChanged;
|
||||||
use crate::model::{Model, Page, PageContent};
|
use crate::model::{Model, Page, PageContent};
|
||||||
use crate::pages::project_settings_page::ProjectSettingsPage;
|
use crate::pages::project_settings_page::ProjectSettingsPage;
|
||||||
use crate::ws::{board_load, send_ws_msg};
|
use crate::ws::{board_load, send_ws_msg, sort_issue_statuses};
|
||||||
use crate::{match_page_mut, FieldId, Msg, PageChanged, ProjectPageChange, WebSocketChanged};
|
use crate::{match_page_mut, FieldId, Msg, PageChanged, ProjectPageChange, WebSocketChanged};
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
@ -114,7 +114,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
))) => page.column_drag.clear_last(),
|
))) => page.column_drag.clear_last(),
|
||||||
Msg::PageChanged(PageChanged::ProjectSettings(
|
Msg::PageChanged(PageChanged::ProjectSettings(
|
||||||
ProjectPageChange::ColumnExchangePosition(issue_bellow_id),
|
ProjectPageChange::ColumnExchangePosition(issue_bellow_id),
|
||||||
)) => exchange_position(issue_bellow_id, model),
|
)) => swap_position(issue_bellow_id, model),
|
||||||
Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDropZone(
|
Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDropZone(
|
||||||
_issue_status_id,
|
_issue_status_id,
|
||||||
))) => {
|
))) => {
|
||||||
@ -124,12 +124,11 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
id,
|
id,
|
||||||
))) => {
|
))) => {
|
||||||
if page.edit_column_id.is_some() && id.is_none() {
|
if page.edit_column_id.is_some() && id.is_none() {
|
||||||
let old_id = page.edit_column_id.as_ref().cloned();
|
if let Some(old_id) = page.edit_column_id {
|
||||||
let name = page.name.value.clone();
|
let name = page.name.value.clone();
|
||||||
if let Some((id, pos)) = model
|
if let Some((id, pos)) = model
|
||||||
.issue_statuses
|
.issue_statuses_by_id
|
||||||
.iter()
|
.get(&old_id)
|
||||||
.find(|is| Some(is.id) == old_id)
|
|
||||||
.map(|is| (is.id, is.position))
|
.map(|is| (is.id, is.position))
|
||||||
{
|
{
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
@ -139,24 +138,21 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
page.name.value = model
|
|
||||||
.issue_statuses
|
|
||||||
.iter()
|
|
||||||
.find_map(|is| {
|
|
||||||
if Some(is.id) == id {
|
|
||||||
Some(is.name.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})
|
if let Some(id) = id {
|
||||||
|
page.name.value = model
|
||||||
|
.issue_statuses_by_id
|
||||||
|
.get(&id)
|
||||||
|
.map(|is| is.name.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
}
|
||||||
page.edit_column_id = id;
|
page.edit_column_id = id;
|
||||||
}
|
}
|
||||||
Msg::PageChanged(PageChanged::ProjectSettings(
|
Msg::PageChanged(PageChanged::ProjectSettings(
|
||||||
ProjectPageChange::SubmitIssueStatusForm,
|
ProjectPageChange::SubmitIssueStatusForm,
|
||||||
)) => {
|
)) => {
|
||||||
let name = page.name.value.clone();
|
let name = page.name.value.clone();
|
||||||
let position = model.issue_statuses.len();
|
let position = model.issue_status_ids.len();
|
||||||
let ws_msg = WsMsgIssueStatus::IssueStatusCreate(name, position as i32).into();
|
let ws_msg = WsMsgIssueStatus::IssueStatusCreate(name, position as i32).into();
|
||||||
send_ws_msg(ws_msg, model.ws.as_ref(), orders);
|
send_ws_msg(ws_msg, model.ws.as_ref(), orders);
|
||||||
}
|
}
|
||||||
@ -164,11 +160,8 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exchange_position(bellow_id: IssueStatusId, model: &mut Model) {
|
fn swap_position(bellow_id: IssueStatusId, model: &mut Model) {
|
||||||
let page = match &mut model.page_content {
|
let page = crate::match_page_mut!(model, ProjectSettings);
|
||||||
PageContent::ProjectSettings(page) => page,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
if page.column_drag.dragged_or_last(bellow_id) {
|
if page.column_drag.dragged_or_last(bellow_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -177,41 +170,29 @@ fn exchange_position(bellow_id: IssueStatusId, model: &mut Model) {
|
|||||||
_ => return log::error!("Nothing is dragged"),
|
_ => return log::error!("Nothing is dragged"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut below = None;
|
let bellow = model
|
||||||
let mut dragged = None;
|
.issue_statuses_by_id
|
||||||
let mut issues_statuses = vec![];
|
.get(&bellow_id)
|
||||||
std::mem::swap(&mut issues_statuses, &mut model.issue_statuses);
|
.map(|is| is.position)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let dragged = model
|
||||||
|
.issue_statuses_by_id
|
||||||
|
.get(&dragged_id)
|
||||||
|
.map(|is| is.position)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
for issue_status in issues_statuses.into_iter() {
|
if let Some(is) = model.issue_statuses_by_id.get_mut(&dragged_id) {
|
||||||
match issue_status.id {
|
is.position = bellow;
|
||||||
id if id == bellow_id => below = Some(issue_status),
|
}
|
||||||
id if id == dragged_id => dragged = Some(issue_status),
|
if let Some(is) = model.issue_statuses_by_id.get_mut(&bellow_id) {
|
||||||
_ => model.issue_statuses.push(issue_status),
|
is.position = dragged;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut below = match below {
|
page.column_drag.mark_dirty(dragged_id);
|
||||||
Some(below) => below,
|
page.column_drag.mark_dirty(bellow_id);
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
let mut dragged = match dragged {
|
|
||||||
Some(issue_status) => issue_status,
|
|
||||||
_ => {
|
|
||||||
model.issue_statuses.push(below);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
std::mem::swap(&mut dragged.position, &mut below.position);
|
|
||||||
|
|
||||||
page.column_drag.mark_dirty(dragged.id);
|
|
||||||
page.column_drag.mark_dirty(below.id);
|
|
||||||
|
|
||||||
model.issue_statuses.push(below);
|
|
||||||
model.issue_statuses.push(dragged);
|
|
||||||
model
|
|
||||||
.issue_statuses
|
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
|
||||||
page.column_drag.last_id = Some(bellow_id);
|
page.column_drag.last_id = Some(bellow_id);
|
||||||
|
sort_issue_statuses(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
@ -224,8 +205,7 @@ fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
_ => return log::error!("bad content type"),
|
_ => return log::error!("bad content type"),
|
||||||
};
|
};
|
||||||
for id in dirty {
|
for id in dirty {
|
||||||
let IssueStatus { name, position, .. } =
|
let IssueStatus { name, position, .. } = match model.issue_statuses_by_id.get(&id) {
|
||||||
match model.issue_statuses.iter().find(|is| is.id == id) {
|
|
||||||
Some(is) => is,
|
Some(is) => is,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
@ -203,7 +203,7 @@ fn category_select_option<'l>(pc: ProjectCategory) -> StyledSelectOption<'l> {
|
|||||||
/// Build draggable columns preview with option to remove and add new columns
|
/// Build draggable columns preview with option to remove and add new columns
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
|
fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
|
||||||
let width = 100f64 / (model.issue_statuses.len() + 1) as f64;
|
let width = 100f64 / (model.issue_status_ids.len() + 1) as f64;
|
||||||
let column_style = format!("width: calc({width}% - 10px)", width = width);
|
let column_style = format!("width: calc({width}% - 10px)", width = width);
|
||||||
let per_column_issue_count = model
|
let per_column_issue_count = model
|
||||||
.issue_ids
|
.issue_ids
|
||||||
@ -216,11 +216,11 @@ fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
|
|||||||
h
|
h
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let columns: Vec<Node<Msg>> = model
|
let columns = model
|
||||||
.issue_statuses
|
.issue_status_ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|is| column_preview(is, page, &per_column_issue_count, column_style.as_str()))
|
.filter_map(|id| model.issue_statuses_by_id.get(id))
|
||||||
.collect();
|
.map(|is| column_preview(is, page, &per_column_issue_count, column_style.as_str()));
|
||||||
|
|
||||||
let columns_section = section![
|
let columns_section = section![
|
||||||
C!["columnsSection"],
|
C!["columnsSection"],
|
||||||
|
@ -81,7 +81,7 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let issue_nav = if model.issue_statuses.is_empty() {
|
let issue_nav = if model.issue_status_ids.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
vec![
|
vec![
|
||||||
|
@ -168,8 +168,15 @@ pub fn change_status(status_id: IssueStatusId, model: &mut Model) -> bool {
|
|||||||
pub fn change_visible(model: &mut Model) {
|
pub fn change_visible(model: &mut Model) {
|
||||||
let visible = ProjectPage::visible_issues(
|
let visible = ProjectPage::visible_issues(
|
||||||
crate::match_page!(model, Project),
|
crate::match_page!(model, Project),
|
||||||
model.epics(),
|
model.epic_ids.len(),
|
||||||
model.issue_statuses(),
|
model
|
||||||
|
.epic_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| model.epics_by_id.get(id)),
|
||||||
|
model
|
||||||
|
.issue_status_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| model.issue_statuses_by_id.get(id)),
|
||||||
model
|
model
|
||||||
.issue_ids
|
.issue_ids
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -197,10 +197,20 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
|
|
||||||
// issue statuses
|
// issue statuses
|
||||||
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusesLoaded(v)) => {
|
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusesLoaded(v)) => {
|
||||||
model.issue_statuses = std::mem::take(v);
|
let len = v.len();
|
||||||
model
|
let mut issue_statuses = std::mem::take(v);
|
||||||
.issue_statuses
|
issue_statuses.sort_by(|a, b| a.position.cmp(&b.position));
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
model.issue_status_ids = Vec::with_capacity(len);
|
||||||
|
|
||||||
|
model.issue_statuses_by_id =
|
||||||
|
issue_statuses
|
||||||
|
.into_iter()
|
||||||
|
.fold(HashMap::with_capacity(len), |mut h, is| {
|
||||||
|
model.issue_status_ids.push(is.id);
|
||||||
|
h.insert(is.id, is);
|
||||||
|
h
|
||||||
|
});
|
||||||
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::IssueStatus,
|
ResourceKind::IssueStatus,
|
||||||
OperationKind::ListLoaded,
|
OperationKind::ListLoaded,
|
||||||
@ -208,42 +218,33 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusCreated(is)) => {
|
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusCreated(is)) => {
|
||||||
let id = is.id;
|
model.issue_status_ids.push(is.id);
|
||||||
model.issue_statuses.push(is.clone());
|
model.issue_statuses_by_id.insert(is.id, is.clone());
|
||||||
model
|
|
||||||
.issue_statuses
|
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::IssueStatus,
|
ResourceKind::IssueStatus,
|
||||||
OperationKind::SingleCreated,
|
OperationKind::SingleCreated,
|
||||||
Some(id),
|
Some(is.id),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusUpdated(changed)) => {
|
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusUpdated(changed)) => {
|
||||||
let id = changed.id;
|
|
||||||
if let Some(idx) = model.issue_statuses.iter().position(|c| c.id == changed.id) {
|
|
||||||
std::mem::swap(&mut model.issue_statuses[idx], changed);
|
|
||||||
}
|
|
||||||
model
|
model
|
||||||
.issue_statuses
|
.issue_statuses_by_id
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
.insert(changed.id, changed.clone());
|
||||||
|
sort_issue_statuses(model);
|
||||||
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::IssueStatus,
|
ResourceKind::IssueStatus,
|
||||||
OperationKind::SingleModified,
|
OperationKind::SingleModified,
|
||||||
Some(id),
|
Some(changed.id),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusDeleted(dropped_id, _count)) => {
|
WsMsg::IssueStatus(WsMsgIssueStatus::IssueStatusDeleted(dropped_id, _count)) => {
|
||||||
let mut old = vec![];
|
model.issue_statuses_by_id.remove(dropped_id);
|
||||||
std::mem::swap(&mut model.issue_statuses, &mut old);
|
model.issue_status_ids = std::mem::take(&mut model.issue_status_ids)
|
||||||
for is in old {
|
.into_iter()
|
||||||
if is.id != *dropped_id {
|
.filter(|id| id != dropped_id)
|
||||||
model.issue_statuses.push(is);
|
.collect();
|
||||||
}
|
|
||||||
}
|
|
||||||
model
|
|
||||||
.issue_statuses
|
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::IssueStatus,
|
ResourceKind::IssueStatus,
|
||||||
OperationKind::SingleRemoved,
|
OperationKind::SingleRemoved,
|
||||||
@ -315,6 +316,11 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
issue.epic_id = o.epic_id;
|
issue.epic_id = o.epic_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
|
ResourceKind::Issue,
|
||||||
|
OperationKind::ListLoaded,
|
||||||
|
None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// users
|
// users
|
||||||
WsMsg::Project(WsMsgProject::ProjectUsersLoaded(v)) => {
|
WsMsg::Project(WsMsgProject::ProjectUsersLoaded(v)) => {
|
||||||
@ -335,12 +341,12 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
Some(modal) => modal.id,
|
Some(modal) => modal.id,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
comments.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
|
||||||
if comments.iter().any(|c| c.issue_id != issue_id) {
|
if comments.iter().any(|c| c.issue_id != issue_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
comments.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
|
|
||||||
model.comments = comments.clone();
|
|
||||||
for comment in std::mem::take(comments) {
|
for comment in std::mem::take(comments) {
|
||||||
|
model.comment_ids.push(comment.id);
|
||||||
model.comments_by_id.insert(comment.id, comment);
|
model.comments_by_id.insert(comment.id, comment);
|
||||||
}
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
@ -351,10 +357,7 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
}
|
}
|
||||||
WsMsg::Comment(WsMsgComment::CommentUpdated(comment)) => {
|
WsMsg::Comment(WsMsgComment::CommentUpdated(comment)) => {
|
||||||
let comment_id = comment.id;
|
let comment_id = comment.id;
|
||||||
if let Some(idx) = model.comments.iter().position(|c| c.id == comment.id) {
|
|
||||||
let _ = std::mem::replace(&mut model.comments[idx], comment.clone());
|
|
||||||
model.comments_by_id.insert(comment.id, comment.clone());
|
model.comments_by_id.insert(comment.id, comment.clone());
|
||||||
}
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Comment,
|
ResourceKind::Comment,
|
||||||
OperationKind::SingleModified,
|
OperationKind::SingleModified,
|
||||||
@ -362,8 +365,8 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::Comment(WsMsgComment::CommentDeleted(comment_id, _count)) => {
|
WsMsg::Comment(WsMsgComment::CommentDeleted(comment_id, _count)) => {
|
||||||
if let Some(idx) = model.comments.iter().position(|c| c.id == *comment_id) {
|
if let Some(idx) = model.comment_ids.iter().position(|id| *id == *comment_id) {
|
||||||
model.comments.remove(idx);
|
model.comment_ids.remove(idx);
|
||||||
}
|
}
|
||||||
model.comments_by_id.remove(&comment_id);
|
model.comments_by_id.remove(&comment_id);
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
@ -407,10 +410,19 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
|
|
||||||
// epics
|
// epics
|
||||||
WsMsg::Epic(WsMsgEpic::EpicsLoaded(epics)) => {
|
WsMsg::Epic(WsMsgEpic::EpicsLoaded(epics)) => {
|
||||||
model.epics = epics.clone();
|
let epics = std::mem::take(epics);
|
||||||
for epic in epics {
|
let len = epics.len();
|
||||||
model.epics_by_id.insert(epic.id, epic.clone());
|
|
||||||
}
|
model.epic_ids = Vec::with_capacity(len);
|
||||||
|
model.epics_by_id =
|
||||||
|
epics
|
||||||
|
.into_iter()
|
||||||
|
.fold(HashMap::with_capacity(len), |mut h, epic| {
|
||||||
|
model.epic_ids.push(epic.id);
|
||||||
|
h.insert(epic.id, epic);
|
||||||
|
h
|
||||||
|
});
|
||||||
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Epic,
|
ResourceKind::Epic,
|
||||||
OperationKind::ListLoaded,
|
OperationKind::ListLoaded,
|
||||||
@ -419,9 +431,10 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
}
|
}
|
||||||
WsMsg::Epic(WsMsgEpic::EpicCreated(epic)) => {
|
WsMsg::Epic(WsMsgEpic::EpicCreated(epic)) => {
|
||||||
let id = epic.id;
|
let id = epic.id;
|
||||||
model.epics.push(epic.clone());
|
model.epic_ids.push(epic.id);
|
||||||
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
model.epic_ids.sort();
|
||||||
model.epics_by_id.insert(epic.id, epic.clone());
|
model.epics_by_id.insert(epic.id, epic.clone());
|
||||||
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Epic,
|
ResourceKind::Epic,
|
||||||
OperationKind::SingleCreated,
|
OperationKind::SingleCreated,
|
||||||
@ -430,11 +443,8 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
}
|
}
|
||||||
WsMsg::Epic(WsMsgEpic::EpicUpdated(epic)) => {
|
WsMsg::Epic(WsMsgEpic::EpicUpdated(epic)) => {
|
||||||
let epic_id = epic.id;
|
let epic_id = epic.id;
|
||||||
if let Some(idx) = model.epics.iter().position(|e| e.id == epic.id) {
|
|
||||||
let _ = std::mem::replace(&mut model.epics[idx], epic.clone());
|
|
||||||
}
|
|
||||||
model.epics_by_id.insert(epic.id, epic.clone());
|
model.epics_by_id.insert(epic.id, epic.clone());
|
||||||
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Epic,
|
ResourceKind::Epic,
|
||||||
OperationKind::SingleModified,
|
OperationKind::SingleModified,
|
||||||
@ -442,11 +452,11 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::Epic(WsMsgEpic::EpicDeleted(id, _count)) => {
|
WsMsg::Epic(WsMsgEpic::EpicDeleted(id, _count)) => {
|
||||||
if let Some(idx) = model.epics.iter().position(|e| e.id == *id) {
|
|
||||||
model.epics.remove(idx);
|
|
||||||
}
|
|
||||||
model.epics_by_id.remove(id);
|
model.epics_by_id.remove(id);
|
||||||
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
model.epic_ids = std::mem::take(&mut model.epic_ids)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|epic_id| epic_id != id)
|
||||||
|
.collect();
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Epic,
|
ResourceKind::Epic,
|
||||||
OperationKind::SingleRemoved,
|
OperationKind::SingleRemoved,
|
||||||
@ -481,6 +491,17 @@ pub fn update(msg: &mut WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sort_issue_statuses(model: &mut Model) {
|
||||||
|
let mut ids = model
|
||||||
|
.issue_status_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| model.issue_statuses_by_id.get(id))
|
||||||
|
.map(|is| (is.id, is.position))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
ids.sort_by(|(_, a), (_, b)| a.cmp(b));
|
||||||
|
model.issue_status_ids = ids.into_iter().map(|(id, _)| id).collect();
|
||||||
|
}
|
||||||
|
|
||||||
fn init_current_project(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
fn init_current_project(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
if model.projects.is_empty() {
|
if model.projects.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user