Start edit epic
This commit is contained in:
parent
b137b461a8
commit
82eb025359
@ -18,7 +18,7 @@ pub enum BoardPageChange {
|
|||||||
IssueDragStarted(EpicId),
|
IssueDragStarted(EpicId),
|
||||||
IssueDragStopped(EpicId),
|
IssueDragStopped(EpicId),
|
||||||
DragLeave(EpicId),
|
DragLeave(EpicId),
|
||||||
ExchangePosition(EpicId),
|
ChangePosition(EpicId),
|
||||||
IssueDragOverStatus(IssueStatusId),
|
IssueDragOverStatus(IssueStatusId),
|
||||||
IssueDropZone(IssueStatusId),
|
IssueDropZone(IssueStatusId),
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use jirs_data::{
|
use jirs_data::{
|
||||||
CommentFieldId, InviteFieldId, IssueFieldId, ProjectFieldId, SignInFieldId, SignUpFieldId,
|
CommentFieldId, EpicFieldId, InviteFieldId, IssueFieldId, ProjectFieldId, SignInFieldId,
|
||||||
UsersFieldId,
|
SignUpFieldId, UsersFieldId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type AvatarFilterActive = bool;
|
pub type AvatarFilterActive = bool;
|
||||||
@ -92,6 +92,8 @@ pub enum FieldId {
|
|||||||
// issue
|
// issue
|
||||||
AddIssueModal(IssueFieldId),
|
AddIssueModal(IssueFieldId),
|
||||||
EditIssueModal(EditIssueModalSection),
|
EditIssueModal(EditIssueModalSection),
|
||||||
|
// epic
|
||||||
|
EditEpic(EpicFieldId),
|
||||||
// project boards
|
// project boards
|
||||||
TextFilterBoard,
|
TextFilterBoard,
|
||||||
CopyButtonLabel,
|
CopyButtonLabel,
|
||||||
@ -202,6 +204,12 @@ impl std::fmt::Display for FieldId {
|
|||||||
UsersFieldId::Avatar => f.write_str("profile-avatar"),
|
UsersFieldId::Avatar => f.write_str("profile-avatar"),
|
||||||
UsersFieldId::CurrentProject => f.write_str("profile-currentProject"),
|
UsersFieldId::CurrentProject => f.write_str("profile-currentProject"),
|
||||||
},
|
},
|
||||||
|
FieldId::EditEpic(sub) => match sub {
|
||||||
|
EpicFieldId::Name => f.write_str("epicEpic-name"),
|
||||||
|
EpicFieldId::StartsAt => f.write_str("epicEpic-startsAt"),
|
||||||
|
EpicFieldId::EndsAt => f.write_str("epicEpic-endsAt"),
|
||||||
|
EpicFieldId::TransformInto => f.write_str("epicEpic-transformInto"),
|
||||||
|
},
|
||||||
FieldId::Rte(..) => f.write_str("rte"),
|
FieldId::Rte(..) => f.write_str("rte"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,9 +238,11 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
crate::modals::update(&msg, model, orders);
|
crate::modals::update(&msg, model, orders);
|
||||||
|
|
||||||
match model.page {
|
match model.page {
|
||||||
Page::Project | Page::AddIssue | Page::EditIssue(..) | Page::DeleteEpic(..) => {
|
Page::Project
|
||||||
pages::project_page::update(msg, model, orders)
|
| Page::AddIssue
|
||||||
}
|
| Page::EditIssue(..)
|
||||||
|
| Page::DeleteEpic(..)
|
||||||
|
| Page::EditEpic(..) => pages::project_page::update(msg, model, orders),
|
||||||
Page::ProjectSettings => pages::project_settings_page::update(msg, model, orders),
|
Page::ProjectSettings => pages::project_settings_page::update(msg, model, orders),
|
||||||
Page::SignIn => pages::sign_in_page::update(msg, model, orders),
|
Page::SignIn => pages::sign_in_page::update(msg, model, orders),
|
||||||
Page::SignUp => pages::sign_up_page::update(msg, model, orders),
|
Page::SignUp => pages::sign_up_page::update(msg, model, orders),
|
||||||
@ -256,9 +258,11 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
|
|
||||||
fn view(model: &model::Model) -> Node<Msg> {
|
fn view(model: &model::Model) -> Node<Msg> {
|
||||||
match model.page {
|
match model.page {
|
||||||
Page::Project | Page::AddIssue | Page::DeleteEpic(..) | Page::EditIssue(..) => {
|
Page::Project
|
||||||
pages::project_page::view(model)
|
| Page::AddIssue
|
||||||
}
|
| Page::EditIssue(..)
|
||||||
|
| Page::DeleteEpic(..)
|
||||||
|
| Page::EditEpic(..) => pages::project_page::view(model),
|
||||||
Page::ProjectSettings => pages::project_settings_page::view(model),
|
Page::ProjectSettings => pages::project_settings_page::view(model),
|
||||||
Page::SignIn => pages::sign_in_page::view(model),
|
Page::SignIn => pages::sign_in_page::view(model),
|
||||||
Page::SignUp => pages::sign_up_page::view(model),
|
Page::SignUp => pages::sign_up_page::view(model),
|
||||||
@ -280,10 +284,6 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
Some(Ok(id)) => Page::EditIssue(id),
|
Some(Ok(id)) => Page::EditIssue(id),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
"delete-epic" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
|
||||||
Some(Ok(id)) => Page::DeleteEpic(id),
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
"profile" => Page::Profile,
|
"profile" => Page::Profile,
|
||||||
"add-issue" => Page::AddIssue,
|
"add-issue" => Page::AddIssue,
|
||||||
"project-settings" => Page::ProjectSettings,
|
"project-settings" => Page::ProjectSettings,
|
||||||
@ -292,6 +292,14 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
"invite" => Page::Invite,
|
"invite" => Page::Invite,
|
||||||
"users" => Page::Users,
|
"users" => Page::Users,
|
||||||
"reports" => Page::Reports,
|
"reports" => Page::Reports,
|
||||||
|
"delete-epic" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
||||||
|
Some(Ok(id)) => Page::DeleteEpic(id),
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
"edit-epic" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
||||||
|
Some(Ok(id)) => Page::EditEpic(id),
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
_ => Page::Project,
|
_ => Page::Project,
|
||||||
};
|
};
|
||||||
Some(page)
|
Some(page)
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
use {
|
|
||||||
crate::model,
|
|
||||||
jirs_data::{EpicId, IssueId},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
|
||||||
pub struct Model {
|
|
||||||
pub epic_id: EpicId,
|
|
||||||
pub related_issues: Vec<IssueId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Model {
|
|
||||||
pub fn new(issue_id: i32, model: &mut model::Model) -> Self {
|
|
||||||
let related_issues = model
|
|
||||||
.issues
|
|
||||||
.iter()
|
|
||||||
.filter_map(|issue| {
|
|
||||||
if issue.epic_id == Some(issue_id) {
|
|
||||||
Some(issue.id)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Self {
|
|
||||||
epic_id: issue_id,
|
|
||||||
related_issues,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
20
jirs-client/src/modals/epics_delete/model.rs
Normal file
20
jirs-client/src/modals/epics_delete/model.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use {
|
||||||
|
crate::model,
|
||||||
|
jirs_data::{EpicId, IssueId},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
|
pub struct Model {
|
||||||
|
pub epic_id: EpicId,
|
||||||
|
pub related_issues: Vec<IssueId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new(epic_id: i32, model: &mut model::Model) -> Self {
|
||||||
|
let related_issues = model.epic_issue_ids(epic_id);
|
||||||
|
Self {
|
||||||
|
epic_id,
|
||||||
|
related_issues,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
components::{styled_button::*, styled_confirm_modal::*, styled_icon::*, styled_modal::*},
|
components::{styled_button::*, styled_confirm_modal::*, styled_icon::*, styled_modal::*},
|
||||||
modals::epic_delete::Model,
|
modals::epics_delete::Model,
|
||||||
model,
|
model,
|
||||||
shared::ToNode,
|
shared::ToNode,
|
||||||
Msg,
|
Msg,
|
5
jirs-client/src/modals/epics_edit/mod.rs
Normal file
5
jirs-client/src/modals/epics_edit/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub use {model::*, update::*, view::*};
|
||||||
|
|
||||||
|
mod model;
|
||||||
|
mod update;
|
||||||
|
mod view;
|
47
jirs-client/src/modals/epics_edit/model.rs
Normal file
47
jirs-client/src/modals/epics_edit/model.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use crate::FieldId;
|
||||||
|
use jirs_data::EpicFieldId;
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
components::{styled_input::*, styled_select::StyledSelectState},
|
||||||
|
model,
|
||||||
|
},
|
||||||
|
jirs_data::{EpicId, IssueId},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
|
pub struct Model {
|
||||||
|
pub epic_id: EpicId,
|
||||||
|
pub related_issues: Vec<IssueId>,
|
||||||
|
pub name: StyledInputState,
|
||||||
|
pub transform_into: StyledSelectState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new(epic_id: i32, model: &mut model::Model) -> Self {
|
||||||
|
let name = model
|
||||||
|
.epics_by_id
|
||||||
|
.get(&epic_id)
|
||||||
|
.map(|epic| epic.name.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let related_issues = model
|
||||||
|
.issues()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|issue| {
|
||||||
|
if issue.epic_id == Some(epic_id) {
|
||||||
|
Some(issue.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self {
|
||||||
|
epic_id,
|
||||||
|
related_issues,
|
||||||
|
name: StyledInputState::new(FieldId::EditEpic(EpicFieldId::Name), name),
|
||||||
|
transform_into: StyledSelectState::new(
|
||||||
|
FieldId::EditEpic(EpicFieldId::StartsAt),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
jirs-client/src/modals/epics_edit/update.rs
Normal file
5
jirs-client/src/modals/epics_edit/update.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
use {crate::Msg, seed::prelude::*};
|
||||||
|
|
||||||
|
pub fn update(_msg: &Msg, model: &mut crate::model::Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
let _modal = crate::match_modal_mut!(model, DeleteEpic);
|
||||||
|
}
|
30
jirs-client/src/modals/epics_edit/view.rs
Normal file
30
jirs-client/src/modals/epics_edit/view.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
components::{styled_input::*, styled_modal::*},
|
||||||
|
modals::epics_edit::Model,
|
||||||
|
model,
|
||||||
|
shared::ToNode,
|
||||||
|
FieldId, Msg,
|
||||||
|
},
|
||||||
|
jirs_data::EpicFieldId,
|
||||||
|
seed::{prelude::*, *},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn view(_model: &model::Model, modal: &Model) -> Node<Msg> {
|
||||||
|
let transform = if modal.related_issues.is_empty() {
|
||||||
|
Node::Empty
|
||||||
|
} else {
|
||||||
|
div![]
|
||||||
|
};
|
||||||
|
StyledModal::build()
|
||||||
|
.center()
|
||||||
|
.child(h1!["Edit epic"])
|
||||||
|
.child(
|
||||||
|
StyledInput::build()
|
||||||
|
.build(FieldId::EditEpic(EpicFieldId::Name))
|
||||||
|
.into_node(),
|
||||||
|
)
|
||||||
|
.child(transform)
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
}
|
@ -2,7 +2,8 @@ pub use {epic_field::*, update::*, view::*};
|
|||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod epic_delete;
|
pub mod epics_delete;
|
||||||
|
pub mod epics_edit;
|
||||||
pub mod issue_statuses_delete;
|
pub mod issue_statuses_delete;
|
||||||
pub mod issues_create;
|
pub mod issues_create;
|
||||||
pub mod issues_delete;
|
pub mod issues_delete;
|
||||||
@ -12,3 +13,35 @@ pub mod time_tracking;
|
|||||||
mod epic_field;
|
mod epic_field;
|
||||||
mod update;
|
mod update;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! match_modal {
|
||||||
|
($model: ident, $ty: ident) => {
|
||||||
|
match $model.modals.iter().find_map(|modal| {
|
||||||
|
if let crate::model::ModalType::$ty(modal) = modal {
|
||||||
|
Some(modal)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Some(modal) => modal,
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! match_modal_mut {
|
||||||
|
($model: ident, $ty: ident) => {
|
||||||
|
match $model.modals.iter_mut().find_map(|modal| {
|
||||||
|
if let crate::model::ModalType::$ty(modal) = modal {
|
||||||
|
Some(modal)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Some(modal) => modal,
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ use {
|
|||||||
ws::send_ws_msg,
|
ws::send_ws_msg,
|
||||||
FieldChange, FieldId, Msg, OperationKind, ResourceKind,
|
FieldChange, FieldId, Msg, OperationKind, ResourceKind,
|
||||||
},
|
},
|
||||||
jirs_data::{TimeTracking, WsMsg},
|
jirs_data::{EpicId, IssueId, TimeTracking, WsMsg},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,6 +46,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Msg::ChangePage(Page::DeleteEpic(issue_id)) => {
|
Msg::ChangePage(Page::DeleteEpic(issue_id)) => {
|
||||||
push_delete_epic_modal(*issue_id, model, orders)
|
push_delete_epic_modal(*issue_id, model, orders)
|
||||||
}
|
}
|
||||||
|
Msg::ChangePage(Page::EditEpic(issue_id)) => push_edit_epic_modal(*issue_id, model, orders),
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
Msg::GlobalKeyDown { key, .. } if key.eq("#") => {
|
Msg::GlobalKeyDown { key, .. } if key.eq("#") => {
|
||||||
@ -69,7 +70,8 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
issues_create::update(msg, model, orders);
|
issues_create::update(msg, model, orders);
|
||||||
issues_edit::update(msg, model, orders);
|
issues_edit::update(msg, model, orders);
|
||||||
issue_statuses_delete::update(msg, model, orders);
|
issue_statuses_delete::update(msg, model, orders);
|
||||||
epic_delete::update(msg, model, orders);
|
epics_edit::update(msg, model, orders);
|
||||||
|
epics_delete::update(msg, model, orders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +83,7 @@ fn push_add_modal(model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
|||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_delete_epic_modal(issue_id: i32, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
fn push_edit_modal(issue_id: EpicId, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
use crate::modals::epic_delete::Model;
|
|
||||||
let modal = Model::new(issue_id, model);
|
|
||||||
model.modals.push(ModalType::DeleteEpic(Box::new(modal)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_edit_modal(issue_id: i32, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|
||||||
let time_tracking_type = model
|
let time_tracking_type = model
|
||||||
.project
|
.project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -113,3 +109,15 @@ fn push_edit_modal(issue_id: i32, model: &mut Model, orders: &mut impl Orders<Ms
|
|||||||
);
|
);
|
||||||
model.modals.push(modal);
|
model.modals.push(modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_edit_epic_modal(epic_id: EpicId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
use crate::modals::epics_edit::Model;
|
||||||
|
let modal = Model::new(epic_id, model);
|
||||||
|
model.modals.push(ModalType::EditEpic(Box::new(modal)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_delete_epic_modal(issue_id: IssueId, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
use crate::modals::epics_delete::Model;
|
||||||
|
let modal = Model::new(issue_id, model);
|
||||||
|
model.modals.push(ModalType::DeleteEpic(Box::new(modal)));
|
||||||
|
}
|
||||||
|
@ -14,6 +14,10 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.modals
|
.modals
|
||||||
.iter()
|
.iter()
|
||||||
.map(|modal| match modal {
|
.map(|modal| match modal {
|
||||||
|
// epic
|
||||||
|
ModalType::DeleteEpic(modal) => crate::modals::epics_delete::view(model, modal),
|
||||||
|
ModalType::EditEpic(modal) => crate::modals::epics_edit::view(model, modal),
|
||||||
|
// issue
|
||||||
ModalType::EditIssue(issue_id, modal) => {
|
ModalType::EditIssue(issue_id, modal) => {
|
||||||
if let Some(_issue) = model.issues_by_id.get(issue_id) {
|
if let Some(_issue) = model.issues_by_id.get(issue_id) {
|
||||||
let details = issues_edit::view(model, modal.as_ref());
|
let details = issues_edit::view(model, modal.as_ref());
|
||||||
@ -29,6 +33,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
ModalType::DeleteIssueConfirm(_id) => crate::modals::issues_delete::view(model),
|
ModalType::DeleteIssueConfirm(_id) => crate::modals::issues_delete::view(model),
|
||||||
ModalType::AddIssue(modal) => issues_create::view(model, modal),
|
ModalType::AddIssue(modal) => issues_create::view(model, modal),
|
||||||
|
// comment
|
||||||
ModalType::DeleteCommentConfirm(comment_id) => {
|
ModalType::DeleteCommentConfirm(comment_id) => {
|
||||||
let comment_id = *comment_id;
|
let comment_id = *comment_id;
|
||||||
StyledConfirmModal::build()
|
StyledConfirmModal::build()
|
||||||
@ -47,7 +52,6 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
ModalType::DebugModal => crate::modals::debug::view(model),
|
ModalType::DebugModal => crate::modals::debug::view(model),
|
||||||
ModalType::DeleteEpic(modal) => crate::modals::epic_delete::view(model, modal),
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
section![id!["modals"], modals]
|
section![id!["modals"], modals]
|
||||||
|
@ -26,9 +26,13 @@ pub trait IssueModal {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub enum ModalType {
|
pub enum ModalType {
|
||||||
|
// issue
|
||||||
AddIssue(Box<crate::modals::issues_create::Model>),
|
AddIssue(Box<crate::modals::issues_create::Model>),
|
||||||
EditIssue(EpicId, Box<crate::modals::issues_edit::Model>),
|
EditIssue(EpicId, Box<crate::modals::issues_edit::Model>),
|
||||||
DeleteEpic(Box<crate::modals::epic_delete::Model>),
|
// epic
|
||||||
|
DeleteEpic(Box<crate::modals::epics_delete::Model>),
|
||||||
|
EditEpic(Box<crate::modals::epics_edit::Model>),
|
||||||
|
|
||||||
DeleteIssueConfirm(EpicId),
|
DeleteIssueConfirm(EpicId),
|
||||||
DeleteCommentConfirm(CommentId),
|
DeleteCommentConfirm(CommentId),
|
||||||
TimeTracking(EpicId),
|
TimeTracking(EpicId),
|
||||||
@ -47,10 +51,15 @@ pub struct CommentForm {
|
|||||||
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub enum Page {
|
pub enum Page {
|
||||||
Project,
|
Project,
|
||||||
EditIssue(EpicId),
|
// epic
|
||||||
DeleteEpic(EpicId),
|
DeleteEpic(EpicId),
|
||||||
|
EditEpic(EpicId),
|
||||||
|
// issue
|
||||||
|
EditIssue(EpicId),
|
||||||
AddIssue,
|
AddIssue,
|
||||||
|
// settings
|
||||||
ProjectSettings,
|
ProjectSettings,
|
||||||
|
// auth
|
||||||
SignIn,
|
SignIn,
|
||||||
SignUp,
|
SignUp,
|
||||||
Invite,
|
Invite,
|
||||||
@ -63,8 +72,9 @@ impl Page {
|
|||||||
pub fn to_path(self) -> String {
|
pub fn to_path(self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Page::Project => "/board".to_string(),
|
Page::Project => "/board".to_string(),
|
||||||
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
|
||||||
Page::DeleteEpic(id) => format!("/delete-epic/{id}", id = id),
|
Page::DeleteEpic(id) => format!("/delete-epic/{id}", id = id),
|
||||||
|
Page::EditEpic(id) => format!("/edit-epic/{id}", id = id),
|
||||||
|
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
||||||
Page::AddIssue => "/add-issue".to_string(),
|
Page::AddIssue => "/add-issue".to_string(),
|
||||||
Page::ProjectSettings => "/project-settings".to_string(),
|
Page::ProjectSettings => "/project-settings".to_string(),
|
||||||
Page::SignIn => "/login".to_string(),
|
Page::SignIn => "/login".to_string(),
|
||||||
@ -107,6 +117,25 @@ impl Default for InvitationFormState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! match_page {
|
||||||
|
($model: ident, $ty: ident) => {
|
||||||
|
match &$model.page_content {
|
||||||
|
PageContent::$ty(page) => page,
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! match_page_mut {
|
||||||
|
($model: ident, $ty: ident) => {
|
||||||
|
match &mut $model.page_content {
|
||||||
|
PageContent::$ty(page) => page,
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PageContent {
|
pub enum PageContent {
|
||||||
SignIn(Box<SignInPage>),
|
SignIn(Box<SignInPage>),
|
||||||
@ -145,27 +174,39 @@ pub struct Model {
|
|||||||
pub page: Page,
|
pub page: Page,
|
||||||
pub page_content: PageContent,
|
pub page_content: PageContent,
|
||||||
|
|
||||||
pub project: Option<Project>,
|
|
||||||
|
|
||||||
pub current_user_project: Option<UserProject>,
|
pub current_user_project: Option<UserProject>,
|
||||||
|
|
||||||
pub issues: Vec<Issue>,
|
// issues
|
||||||
|
issues: Vec<Issue>,
|
||||||
pub issues_by_id: HashMap<EpicId, Issue>,
|
pub issues_by_id: HashMap<EpicId, Issue>,
|
||||||
|
|
||||||
|
// users
|
||||||
pub user: Option<User>,
|
pub user: Option<User>,
|
||||||
pub users: Vec<User>,
|
pub users: Vec<User>,
|
||||||
pub users_by_id: HashMap<UserId, User>,
|
pub users_by_id: HashMap<UserId, User>,
|
||||||
|
|
||||||
|
// comments
|
||||||
pub comments: Vec<Comment>,
|
pub comments: Vec<Comment>,
|
||||||
|
pub comments_by_id: HashMap<CommentId, Comment>,
|
||||||
|
|
||||||
|
// issue_statuses
|
||||||
pub issue_statuses: Vec<IssueStatus>,
|
pub issue_statuses: Vec<IssueStatus>,
|
||||||
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>,
|
||||||
|
|
||||||
|
// messages
|
||||||
pub messages: Vec<Message>,
|
pub messages: Vec<Message>,
|
||||||
|
|
||||||
|
// user_projects
|
||||||
pub user_projects: Vec<UserProject>,
|
pub user_projects: Vec<UserProject>,
|
||||||
|
|
||||||
|
// projects
|
||||||
|
pub project: Option<Project>,
|
||||||
pub projects: Vec<Project>,
|
pub projects: Vec<Project>,
|
||||||
|
|
||||||
|
// epics
|
||||||
pub epics: Vec<Epic>,
|
pub epics: Vec<Epic>,
|
||||||
|
pub epics_by_id: HashMap<EpicId, Epic>,
|
||||||
|
|
||||||
pub show_extras: bool,
|
pub show_extras: bool,
|
||||||
}
|
}
|
||||||
@ -194,6 +235,7 @@ impl Model {
|
|||||||
users: vec![],
|
users: vec![],
|
||||||
users_by_id: Default::default(),
|
users_by_id: Default::default(),
|
||||||
comments: vec![],
|
comments: vec![],
|
||||||
|
comments_by_id: Default::default(),
|
||||||
issue_statuses: vec![],
|
issue_statuses: vec![],
|
||||||
issue_statuses_by_id: Default::default(),
|
issue_statuses_by_id: Default::default(),
|
||||||
issue_statuses_by_name: Default::default(),
|
issue_statuses_by_name: Default::default(),
|
||||||
@ -203,13 +245,52 @@ impl Model {
|
|||||||
epics: vec![],
|
epics: vec![],
|
||||||
issues_by_id: Default::default(),
|
issues_by_id: Default::default(),
|
||||||
show_extras: false,
|
show_extras: false,
|
||||||
|
epics_by_id: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn issues(&self) -> &[Issue] {
|
||||||
|
&self.issues
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn issues_mut(&mut self) -> &mut Vec<Issue> {
|
||||||
|
&mut self.issues
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn issue_statuses(&self) -> &[IssueStatus] {
|
||||||
|
&self.issue_statuses
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn epics(&self) -> &[Epic] {
|
||||||
|
&self.epics
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn user(&self) -> &Option<User> {
|
||||||
|
&self.user
|
||||||
|
}
|
||||||
|
|
||||||
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()
|
||||||
.map(|up| up.role)
|
.map(|up| up.role)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn epic_issue_ids(&self, epic_id: EpicId) -> Vec<IssueId> {
|
||||||
|
self.issues()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|issue| {
|
||||||
|
if issue.epic_id == Some(epic_id) {
|
||||||
|
Some(issue.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,66 @@ impl ProjectPage {
|
|||||||
}
|
}
|
||||||
self.visible_issues = map;
|
self.visible_issues = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn visible_issues(
|
||||||
|
page: &ProjectPage,
|
||||||
|
epics: &[Epic],
|
||||||
|
statuses: &[IssueStatus],
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
sorted.sort_by(|(_, a_time), (_, b_time)| a_time.cmp(b_time));
|
||||||
|
issues = sorted
|
||||||
|
.into_iter()
|
||||||
|
.take(10)
|
||||||
|
.flat_map(|(id, _)| m.remove(&id))
|
||||||
|
.collect();
|
||||||
|
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
||||||
|
}
|
||||||
|
|
||||||
|
for epic in epics {
|
||||||
|
let mut per_epic_map = EpicIssuePerStatus {
|
||||||
|
epic_ref: epic.map(|(id, name)| (id, name.to_string())),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),
|
||||||
|
..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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
per_epic_map.per_status_issues.push(per_status_map);
|
||||||
|
}
|
||||||
|
map.push(per_epic_map);
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -25,10 +25,9 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_page = match &mut model.page_content {
|
let mut rebuild_visible = false;
|
||||||
PageContent::Project(project_page) => project_page,
|
{
|
||||||
_ => return,
|
let project_page = crate::match_page_mut!(model, Project);
|
||||||
};
|
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::UserChanged(..)
|
Msg::UserChanged(..)
|
||||||
@ -40,12 +39,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
}
|
}
|
||||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleRemoved, ..) => {
|
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleRemoved, ..) => {
|
||||||
orders.skip().send_msg(Msg::ModalDropped);
|
orders.skip().send_msg(Msg::ModalDropped);
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::ResourceChanged(
|
Msg::ResourceChanged(
|
||||||
ResourceKind::Issue
|
ResourceKind::Issue
|
||||||
@ -54,12 +48,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
| ResourceKind::Epic,
|
| ResourceKind::Epic,
|
||||||
..,
|
..,
|
||||||
) => {
|
) => {
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
||||||
@ -79,12 +68,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
}
|
}
|
||||||
Msg::StrInputChanged(FieldId::TextFilterBoard, text) => {
|
Msg::StrInputChanged(FieldId::TextFilterBoard, text) => {
|
||||||
project_page.text_filter = text;
|
project_page.text_filter = text;
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::ProjectAvatarFilterChanged(user_id, active) => {
|
Msg::ProjectAvatarFilterChanged(user_id, active) => {
|
||||||
if active {
|
if active {
|
||||||
@ -96,41 +80,21 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
} else {
|
} else {
|
||||||
project_page.active_avatar_filters.push(user_id);
|
project_page.active_avatar_filters.push(user_id);
|
||||||
}
|
}
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::ProjectToggleOnlyMy => {
|
Msg::ProjectToggleOnlyMy => {
|
||||||
project_page.only_my_filter = !project_page.only_my_filter;
|
project_page.only_my_filter = !project_page.only_my_filter;
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::ProjectToggleRecentlyUpdated => {
|
Msg::ProjectToggleRecentlyUpdated => {
|
||||||
project_page.recently_updated_filter = !project_page.recently_updated_filter;
|
project_page.recently_updated_filter = !project_page.recently_updated_filter;
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::ProjectClearFilters => {
|
Msg::ProjectClearFilters => {
|
||||||
project_page.active_avatar_filters = vec![];
|
project_page.active_avatar_filters = vec![];
|
||||||
project_page.recently_updated_filter = false;
|
project_page.recently_updated_filter = false;
|
||||||
project_page.only_my_filter = false;
|
project_page.only_my_filter = false;
|
||||||
project_page.rebuild_visible(
|
rebuild_visible = true;
|
||||||
&model.epics,
|
|
||||||
&model.issue_statuses,
|
|
||||||
&model.issues,
|
|
||||||
&model.user,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStarted(issue_id))) => {
|
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStarted(issue_id))) => {
|
||||||
crate::ws::issue::drag_started(issue_id, model)
|
crate::ws::issue::drag_started(issue_id, model)
|
||||||
@ -138,9 +102,9 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStopped(_))) => {
|
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStopped(_))) => {
|
||||||
crate::ws::issue::sync(model, orders);
|
crate::ws::issue::sync(model, orders);
|
||||||
}
|
}
|
||||||
Msg::PageChanged(PageChanged::Board(BoardPageChange::ExchangePosition(
|
Msg::PageChanged(PageChanged::Board(BoardPageChange::ChangePosition(
|
||||||
issue_bellow_id,
|
issue_bellow_id,
|
||||||
))) => crate::ws::issue::exchange_position(issue_bellow_id, model),
|
))) => crate::ws::issue::change_position(issue_bellow_id, model),
|
||||||
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragOverStatus(status))) => {
|
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragOverStatus(status))) => {
|
||||||
crate::ws::issue::change_status(status, model)
|
crate::ws::issue::change_status(status, model)
|
||||||
}
|
}
|
||||||
@ -159,6 +123,17 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if rebuild_visible {
|
||||||
|
let visible_issues = ProjectPage::visible_issues(
|
||||||
|
crate::match_page!(model, Project),
|
||||||
|
model.epics(),
|
||||||
|
model.issue_statuses(),
|
||||||
|
model.issues(),
|
||||||
|
model.user(),
|
||||||
|
);
|
||||||
|
crate::match_page_mut!(model, Project).visible_issues = visible_issues;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_page_content(model: &mut Model) {
|
fn build_page_content(model: &mut Model) {
|
||||||
|
@ -38,6 +38,15 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
|||||||
let edit_button = StyledButton::build()
|
let edit_button = StyledButton::build()
|
||||||
.empty()
|
.empty()
|
||||||
.icon(Icon::EditAlt)
|
.icon(Icon::EditAlt)
|
||||||
|
.on_click(mouse_ev("click", move |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
seed::Url::new()
|
||||||
|
.add_path_part("edit-epic")
|
||||||
|
.add_path_part(id.to_string())
|
||||||
|
.go_and_push();
|
||||||
|
Msg::ChangePage(Page::EditEpic(id))
|
||||||
|
}))
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let delete_button = StyledButton::build()
|
let delete_button = StyledButton::build()
|
||||||
@ -159,7 +168,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
|
|||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Some(Msg::PageChanged(PageChanged::Board(
|
Some(Msg::PageChanged(PageChanged::Board(
|
||||||
BoardPageChange::ExchangePosition(issue_id),
|
BoardPageChange::ChangePosition(issue_id),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ fn columns_section(model: &Model, page: &ProjectSettingsPage) -> Node<Msg> {
|
|||||||
let width = 100f64 / (model.issue_statuses.len() + 1) as f64;
|
let width = 100f64 / (model.issue_statuses.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 mut per_column_issue_count = HashMap::new();
|
let mut per_column_issue_count = HashMap::new();
|
||||||
for issue in model.issues.iter() {
|
for issue in model.issues().iter() {
|
||||||
*per_column_issue_count
|
*per_column_issue_count
|
||||||
.entry(issue.issue_status_id)
|
.entry(issue.issue_status_id)
|
||||||
.or_insert(0) += 1;
|
.or_insert(0) += 1;
|
||||||
|
@ -196,7 +196,7 @@ fn issue_list(page: &ReportsPage, this_month_updated: &[&Issue]) -> Node<Msg> {
|
|||||||
|
|
||||||
fn this_month_updated<'a>(model: &'a Model, page: &ReportsPage) -> Vec<&'a Issue> {
|
fn this_month_updated<'a>(model: &'a Model, page: &ReportsPage) -> Vec<&'a Issue> {
|
||||||
model
|
model
|
||||||
.issues
|
.issues()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|issue| {
|
.filter(|issue| {
|
||||||
issue.updated_at.date() >= page.first_day && issue.updated_at.date() <= page.last_day
|
issue.updated_at.date() >= page.first_day && issue.updated_at.date() <= page.last_day
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
model::{Model, PageContent},
|
model::{Model, PageContent},
|
||||||
|
pages::project_page::ProjectPage,
|
||||||
ws::send_ws_msg,
|
ws::send_ws_msg,
|
||||||
Msg,
|
Msg,
|
||||||
},
|
},
|
||||||
@ -16,7 +17,7 @@ pub fn drag_started(issue_id: EpicId, model: &mut Model) {
|
|||||||
project_page.issue_drag.drag(issue_id);
|
project_page.issue_drag.drag(issue_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exchange_position(below_id: EpicId, model: &mut Model) {
|
pub fn change_position(below_id: EpicId, model: &mut Model) {
|
||||||
let project_page = match &mut model.page_content {
|
let project_page = match &mut model.page_content {
|
||||||
PageContent::Project(project_page) => project_page,
|
PageContent::Project(project_page) => project_page,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -30,7 +31,7 @@ pub fn exchange_position(below_id: EpicId, model: &mut Model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (issue_status_id, epic_id) = model
|
let (issue_status_id, epic_id) = model
|
||||||
.issues
|
.issues()
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|issue| {
|
.find_map(|issue| {
|
||||||
if issue.id == dragged_id {
|
if issue.id == dragged_id {
|
||||||
@ -42,7 +43,7 @@ pub fn exchange_position(below_id: EpicId, model: &mut Model) {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut issues: Vec<Issue> = model
|
let mut issues: Vec<Issue> = model
|
||||||
.issues
|
.issues_mut()
|
||||||
.drain_filter(|issue| issue.issue_status_id == issue_status_id && issue.epic_id == epic_id)
|
.drain_filter(|issue| issue.issue_status_id == issue_status_id && issue.epic_id == epic_id)
|
||||||
.collect();
|
.collect();
|
||||||
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
||||||
@ -66,16 +67,18 @@ pub fn exchange_position(below_id: EpicId, model: &mut Model) {
|
|||||||
iss.list_position = issue.list_position;
|
iss.list_position = issue.list_position;
|
||||||
}
|
}
|
||||||
changed.push((issue.id, issue.list_position));
|
changed.push((issue.id, issue.list_position));
|
||||||
model.issues.push(issue);
|
model.issues_mut().push(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let PageContent::Project(project_page) = &mut model.page_content {
|
let visible = ProjectPage::visible_issues(
|
||||||
project_page.rebuild_visible(
|
crate::match_page!(model, Project),
|
||||||
&model.epics,
|
model.epics(),
|
||||||
&model.issue_statuses,
|
model.issue_statuses(),
|
||||||
&model.issues,
|
model.issues(),
|
||||||
&model.user,
|
model.user(),
|
||||||
);
|
);
|
||||||
|
if let PageContent::Project(project_page) = &mut model.page_content {
|
||||||
|
project_page.visible_issues = visible;
|
||||||
for (id, _) in changed.iter() {
|
for (id, _) in changed.iter() {
|
||||||
project_page.issue_drag.mark_dirty(*id);
|
project_page.issue_drag.mark_dirty(*id);
|
||||||
}
|
}
|
||||||
@ -107,18 +110,16 @@ pub fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
model.ws.as_ref(),
|
model.ws.as_ref(),
|
||||||
orders,
|
orders,
|
||||||
);
|
);
|
||||||
if let PageContent::Project(project_page) = &mut model.page_content {
|
crate::match_page_mut!(model, Project).issue_drag.clear();
|
||||||
project_page.issue_drag.clear()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_status(status_id: IssueStatusId, model: &mut Model) {
|
pub fn change_status(status_id: IssueStatusId, model: &mut Model) {
|
||||||
let project_page = match &mut model.page_content {
|
let dragged_id = match crate::match_page!(model, Project)
|
||||||
PageContent::Project(project_page) => project_page,
|
.issue_drag
|
||||||
_ => return,
|
.dragged_id
|
||||||
};
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
let dragged_id = match project_page.issue_drag.dragged_id.as_ref().cloned() {
|
{
|
||||||
Some(issue_id) => issue_id,
|
Some(issue_id) => issue_id,
|
||||||
_ => return error!("Nothing is dragged"),
|
_ => return error!("Nothing is dragged"),
|
||||||
};
|
};
|
||||||
@ -132,7 +133,7 @@ pub fn change_status(status_id: IssueStatusId, model: &mut Model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut issues: Vec<Issue> = model
|
let mut issues: Vec<Issue> = model
|
||||||
.issues
|
.issues_mut()
|
||||||
.drain_filter(|issue| {
|
.drain_filter(|issue| {
|
||||||
if issue.id == dragged_id {
|
if issue.id == dragged_id {
|
||||||
issue.issue_status_id = status_id;
|
issue.issue_status_id = status_id;
|
||||||
@ -143,6 +144,7 @@ pub fn change_status(status_id: IssueStatusId, model: &mut Model) {
|
|||||||
|
|
||||||
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
issues.sort_by(|a, b| a.list_position.cmp(&b.list_position));
|
||||||
|
|
||||||
|
let mut dirty = vec![];
|
||||||
for mut issue in issues {
|
for mut issue in issues {
|
||||||
if issue.id == dragged_id {
|
if issue.id == dragged_id {
|
||||||
issue.issue_status_id = status_id;
|
issue.issue_status_id = status_id;
|
||||||
@ -150,14 +152,24 @@ pub fn change_status(status_id: IssueStatusId, model: &mut Model) {
|
|||||||
iss.issue_status_id = status_id;
|
iss.issue_status_id = status_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
project_page.issue_drag.mark_dirty(issue.id);
|
|
||||||
model.issues.push(issue);
|
dirty.push(issue.id);
|
||||||
|
model.issues_mut().push(issue);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let project_page = crate::match_page_mut!(model, Project);
|
||||||
|
for id in dirty {
|
||||||
|
project_page.issue_drag.mark_dirty(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
project_page.rebuild_visible(
|
let visible = ProjectPage::visible_issues(
|
||||||
&model.epics,
|
crate::match_page!(model, Project),
|
||||||
&model.issue_statuses,
|
model.epics(),
|
||||||
&model.issues,
|
model.issue_statuses(),
|
||||||
&model.user,
|
model.issues(),
|
||||||
|
model.user(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
crate::match_page_mut!(model, Project).visible_issues = visible;
|
||||||
}
|
}
|
||||||
|
@ -219,9 +219,11 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
// issues
|
// issues
|
||||||
WsMsg::ProjectIssuesLoaded(mut v) => {
|
WsMsg::ProjectIssuesLoaded(mut v) => {
|
||||||
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
|
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
|
||||||
model.issues = v;
|
{
|
||||||
|
let _ = std::mem::replace(model.issues_mut(), v.clone());
|
||||||
|
};
|
||||||
model.issues_by_id.clear();
|
model.issues_by_id.clear();
|
||||||
for issue in model.issues.iter() {
|
for issue in v {
|
||||||
model.issues_by_id.insert(issue.id, issue.clone());
|
model.issues_by_id.insert(issue.id, issue.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,12 +233,12 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::IssueUpdated(mut issue) => {
|
WsMsg::IssueUpdated(issue) => {
|
||||||
let id = issue.id;
|
let id = issue.id;
|
||||||
model.issues_by_id.remove(&id);
|
model.issues_by_id.remove(&id);
|
||||||
model.issues_by_id.insert(id, issue.clone());
|
model.issues_by_id.insert(id, issue.clone());
|
||||||
if let Some(idx) = model.issues.iter().position(|i| i.id == issue.id) {
|
if let Some(idx) = model.issues().iter().position(|i| i.id == issue.id) {
|
||||||
std::mem::swap(&mut model.issues[idx], &mut issue);
|
let _ = std::mem::replace(&mut model.issues_mut()[idx], issue);
|
||||||
}
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Issue,
|
ResourceKind::Issue,
|
||||||
@ -246,12 +248,12 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
WsMsg::IssueDeleted(id, _count) => {
|
WsMsg::IssueDeleted(id, _count) => {
|
||||||
let mut old = vec![];
|
let mut old = vec![];
|
||||||
std::mem::swap(&mut model.issues, &mut old);
|
std::mem::swap(model.issues_mut(), &mut old);
|
||||||
for is in old {
|
for is in old {
|
||||||
if is.id == id {
|
if is.id == id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
model.issues.push(is);
|
model.issues_mut().push(is);
|
||||||
}
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Issue,
|
ResourceKind::Issue,
|
||||||
@ -282,27 +284,33 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
comments.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
|
comments.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
|
||||||
model.comments = comments;
|
model.comments = comments.clone();
|
||||||
|
for comment in comments {
|
||||||
|
model.comments_by_id.insert(comment.id, comment);
|
||||||
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Comment,
|
ResourceKind::Comment,
|
||||||
OperationKind::ListLoaded,
|
OperationKind::ListLoaded,
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::CommentUpdated(mut comment) => {
|
WsMsg::CommentUpdated(comment) => {
|
||||||
|
let comment_id = comment.id;
|
||||||
if let Some(idx) = model.comments.iter().position(|c| c.id == comment.id) {
|
if let Some(idx) = model.comments.iter().position(|c| c.id == comment.id) {
|
||||||
std::mem::swap(&mut model.comments[idx], &mut comment);
|
let _ = std::mem::replace(&mut model.comments[idx], comment.clone());
|
||||||
|
model.comments_by_id.insert(comment.id, comment);
|
||||||
}
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Comment,
|
ResourceKind::Comment,
|
||||||
OperationKind::SingleModified,
|
OperationKind::SingleModified,
|
||||||
Some(comment.id),
|
Some(comment_id),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::CommentDeleted(comment_id, _count) => {
|
WsMsg::CommentDeleted(comment_id, _count) => {
|
||||||
if let Some(idx) = model.comments.iter().position(|c| c.id == comment_id) {
|
if let Some(idx) = model.comments.iter().position(|c| c.id == comment_id) {
|
||||||
model.comments.remove(idx);
|
model.comments.remove(idx);
|
||||||
}
|
}
|
||||||
|
model.comments_by_id.remove(&comment_id);
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Comment,
|
ResourceKind::Comment,
|
||||||
OperationKind::SingleRemoved,
|
OperationKind::SingleRemoved,
|
||||||
@ -317,7 +325,7 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
if let Some(me) = model.user.as_mut() {
|
if let Some(me) = model.user.as_mut() {
|
||||||
if me.id == user_id {
|
if me.id == user_id {
|
||||||
me.avatar_url = Some(avatar_url.clone());
|
me.avatar_url = Some(avatar_url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
@ -361,7 +369,10 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
|
|
||||||
// epics
|
// epics
|
||||||
WsMsg::EpicsLoaded(epics) => {
|
WsMsg::EpicsLoaded(epics) => {
|
||||||
model.epics = epics;
|
model.epics = epics.clone();
|
||||||
|
for epic in epics {
|
||||||
|
model.epics_by_id.insert(epic.id, epic);
|
||||||
|
}
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Epic,
|
ResourceKind::Epic,
|
||||||
OperationKind::ListLoaded,
|
OperationKind::ListLoaded,
|
||||||
@ -370,29 +381,33 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
WsMsg::EpicCreated(epic) => {
|
WsMsg::EpicCreated(epic) => {
|
||||||
let id = epic.id;
|
let id = epic.id;
|
||||||
model.epics.push(epic);
|
model.epics.push(epic.clone());
|
||||||
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
model.epics_by_id.insert(epic.id, epic);
|
||||||
orders.send_msg(Msg::ResourceChanged(
|
orders.send_msg(Msg::ResourceChanged(
|
||||||
ResourceKind::Epic,
|
ResourceKind::Epic,
|
||||||
OperationKind::SingleCreated,
|
OperationKind::SingleCreated,
|
||||||
Some(id),
|
Some(id),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::EpicUpdated(mut epic) => {
|
WsMsg::EpicUpdated(epic) => {
|
||||||
|
let epic_id = epic.id;
|
||||||
if let Some(idx) = model.epics.iter().position(|e| e.id == epic.id) {
|
if let Some(idx) = model.epics.iter().position(|e| e.id == epic.id) {
|
||||||
std::mem::swap(&mut model.epics[idx], &mut epic);
|
let _ = std::mem::replace(&mut model.epics[idx], epic.clone());
|
||||||
}
|
}
|
||||||
|
model.epics_by_id.insert(epic.id, epic);
|
||||||
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
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,
|
||||||
Some(epic.id),
|
Some(epic_id),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::EpicDeleted(id, _count) => {
|
WsMsg::EpicDeleted(id, _count) => {
|
||||||
if let Some(idx) = model.epics.iter().position(|e| e.id == id) {
|
if let Some(idx) = model.epics.iter().position(|e| e.id == id) {
|
||||||
model.epics.remove(idx);
|
model.epics.remove(idx);
|
||||||
}
|
}
|
||||||
|
model.epics_by_id.remove(&id);
|
||||||
model.epics.sort_by(|a, b| a.id.cmp(&b.id));
|
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,
|
||||||
|
@ -65,4 +65,5 @@ pub enum EpicFieldId {
|
|||||||
Name,
|
Name,
|
||||||
StartsAt,
|
StartsAt,
|
||||||
EndsAt,
|
EndsAt,
|
||||||
|
TransformInto,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user