diff --git a/jirs-client/src/lib.rs b/jirs-client/src/lib.rs index f7bba78e..13a7088b 100644 --- a/jirs-client/src/lib.rs +++ b/jirs-client/src/lib.rs @@ -86,7 +86,9 @@ pub enum Msg { // dragging IssueDragStarted(IssueId), IssueDragStopped(IssueId), + ExchangePosition(IssueId), IssueDropZone(IssueStatus), + UnlockDragOver, // inputs InputChanged(FieldId, String), diff --git a/jirs-client/src/modal/add_issue.rs b/jirs-client/src/modal/add_issue.rs index 8f194680..a6da37e7 100644 --- a/jirs-client/src/modal/add_issue.rs +++ b/jirs-client/src/modal/add_issue.rs @@ -115,7 +115,6 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde _ => (), } - log!(modal); } pub fn view(model: &Model, modal: &AddIssueModal) -> Node { diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index 201a76b5..7bfcefc8 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -118,6 +118,7 @@ pub struct ProjectPage { pub only_my_filter: bool, pub recently_updated_filter: bool, pub dragged_issue_id: Option, + pub drag_locked: bool, } #[derive(Debug)] @@ -167,6 +168,7 @@ impl Default for Model { only_my_filter: false, recently_updated_filter: false, dragged_issue_id: None, + drag_locked: false, }, modals: vec![], project: None, diff --git a/jirs-client/src/project.rs b/jirs-client/src/project.rs index bdea5619..4147b4b4 100644 --- a/jirs-client/src/project.rs +++ b/jirs-client/src/project.rs @@ -10,9 +10,9 @@ use crate::shared::styled_icon::{Icon, StyledIcon}; use crate::shared::styled_input::StyledInput; use crate::shared::styled_select::StyledSelectChange; use crate::shared::{drag_ev, inner_layout, ToNode}; -use crate::{FieldId, Msg}; +use crate::{FieldId, Msg, APP}; -pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orders) { +pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders) { match msg { Msg::ChangePage(Page::Project) | Msg::ChangePage(Page::AddIssue) @@ -41,6 +41,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orde model.issues.push(is); } } + orders.skip().send_msg(Msg::ModalDropped); } Msg::ToggleAboutTooltip => { model.project_page.about_tooltip_visible = !model.project_page.about_tooltip_visible; @@ -97,13 +98,73 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orde Msg::IssueDragStopped(_) => { model.project_page.dragged_issue_id = None; } + Msg::ExchangePosition(issue_bellow_id) => { + if model.project_page.drag_locked { + return; + } + log!(issue_bellow_id); + log!(model.project_page.dragged_issue_id); + let dragged_id = match model.project_page.dragged_issue_id { + Some(id) => id, + _ => return, + }; + + let mut below = None; + let mut dragged = None; + let mut issues = vec![]; + std::mem::swap(&mut issues, &mut model.issues); + + for issue in issues.into_iter() { + match issue.id { + id if id == issue_bellow_id => below = Some(issue), + id if id == dragged_id => dragged = Some(issue), + _ => model.issues.push(issue), + }; + } + + let mut below = match below { + Some(below) => below, + _ => return, + }; + let mut dragged = match dragged { + Some(issue) => issue, + _ => { + model.issues.push(below); + return; + } + }; + if dragged.status == below.status { + std::mem::swap(&mut dragged.list_position, &mut below.list_position); + below.status = dragged.status.clone(); + } else { + below.list_position = model + .issues + .iter() + .map(|i| i.list_position) + .max() + .unwrap_or(0) + + 1; + std::mem::swap(&mut dragged.list_position, &mut below.list_position); + below.status = dragged.status.clone(); + } + model.issues.push(below); + model.issues.push(dragged); + model + .issues + .sort_by(|a, b| a.list_position.cmp(&b.list_position)); + model.project_page.drag_locked = true; + // log!(model.issues); + } + Msg::UnlockDragOver => { + model.project_page.drag_locked = false; + } Msg::IssueDropZone(status) => match model.project_page.dragged_issue_id.as_ref().cloned() { Some(issue_id) => { - let mut position = 0f64; + let mut position = 0; let mut found: Option<&mut Issue> = None; for issue in model.issues.iter_mut() { if issue.status == status { - position += 1f64; + position += 1; } if issue.id == issue_id { found = Some(issue); @@ -116,7 +177,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orde }; issue.status = status.clone(); - issue.list_position = position + 1f64; + issue.list_position = position + 1; let payload = UpdateIssuePayload { title: Some(issue.title.clone()), @@ -138,7 +199,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, _orders: &mut impl Orde _ => error!("Drag stopped before drop :("), }, Msg::DeleteIssue(issue_id) => { - send_ws_msg(jirs_data::WsMsg::IssueDeleted(issue_id)); + send_ws_msg(jirs_data::WsMsg::IssueDeleteRequest(issue_id)); } _ => (), } @@ -356,10 +417,25 @@ fn project_issue(model: &Model, issue: &Issue) -> Node { let issue_id = issue.id; let drag_started = drag_ev(Ev::DragStart, move |_| Msg::IssueDragStarted(issue_id)); let drag_stopped = drag_ev(Ev::DragEnd, move |_| Msg::IssueDragStopped(issue_id)); + let drag_over_handler = drag_ev(Ev::DragOver, move |ev| { + ev.prevent_default(); + ev.stop_propagation(); + seed::set_timeout( + Box::new(|| { + let app = match unsafe { APP.as_mut().unwrap() }.write() { + Ok(app) => app, + _ => return, + }; + app.update(Msg::UnlockDragOver); + }), + 3000, + ); + Msg::ExchangePosition(issue_id) + }); - let mut class_list = vec!["issue"]; + let class_list = vec!["issue"]; if Some(issue_id) == model.project_page.dragged_issue_id { - class_list.push("hidden"); + // class_list.push("hidden"); } let href = format!("/issues/{id}", id = issue_id); @@ -370,6 +446,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node { div![ attrs![At::Class => class_list.join(" "), At::Draggable => true], drag_stopped, + drag_over_handler, p![attrs![At::Class => "title"], issue.title,], div![ attrs![At::Class => "bottom"], diff --git a/jirs-client/src/ws/mod.rs b/jirs-client/src/ws/mod.rs index a28cb987..9b418558 100644 --- a/jirs-client/src/ws/mod.rs +++ b/jirs-client/src/ws/mod.rs @@ -25,7 +25,9 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders model.user = Some(user.clone()); } Msg::WsMsg(WsMsg::ProjectIssuesLoaded(v)) => { - model.issues = v.clone(); + let mut v = v.clone(); + v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64))); + model.issues = v; } Msg::WsMsg(WsMsg::ProjectUsersLoaded(v)) => { model.users = v.clone(); diff --git a/jirs-data/src/lib.rs b/jirs-data/src/lib.rs index e0f79b8c..b37a11e6 100644 --- a/jirs-data/src/lib.rs +++ b/jirs-data/src/lib.rs @@ -223,7 +223,7 @@ pub struct FullIssue { pub issue_type: IssueType, pub status: IssueStatus, pub priority: IssuePriority, - pub list_position: f64, + pub list_position: i32, pub description: Option, pub description_text: Option, pub estimate: Option, @@ -282,7 +282,7 @@ pub struct Issue { pub issue_type: IssueType, pub status: IssueStatus, pub priority: IssuePriority, - pub list_position: f64, + pub list_position: i32, pub description: Option, pub description_text: Option, pub estimate: Option, @@ -340,7 +340,7 @@ pub struct UpdateIssuePayload { pub issue_type: Option, pub status: Option, pub priority: Option, - pub list_position: Option, + pub list_position: Option, pub description: Option>, pub description_text: Option>, pub estimate: Option>, diff --git a/jirs-server/migrations/2020-04-08-210310_change_list_position_type/down.sql b/jirs-server/migrations/2020-04-08-210310_change_list_position_type/down.sql new file mode 100644 index 00000000..945d0780 --- /dev/null +++ b/jirs-server/migrations/2020-04-08-210310_change_list_position_type/down.sql @@ -0,0 +1 @@ +alter table issues alter column list_position set data type number; diff --git a/jirs-server/migrations/2020-04-08-210310_change_list_position_type/up.sql b/jirs-server/migrations/2020-04-08-210310_change_list_position_type/up.sql new file mode 100644 index 00000000..525b1ea9 --- /dev/null +++ b/jirs-server/migrations/2020-04-08-210310_change_list_position_type/up.sql @@ -0,0 +1 @@ +alter table issues alter column list_position set data type integer; diff --git a/jirs-server/src/db/issues.rs b/jirs-server/src/db/issues.rs index 4c24da26..c80a536b 100644 --- a/jirs-server/src/db/issues.rs +++ b/jirs-server/src/db/issues.rs @@ -76,7 +76,7 @@ pub struct UpdateIssue { pub issue_type: Option, pub status: Option, pub priority: Option, - pub list_position: Option, + pub list_position: Option, pub description: Option>, pub description_text: Option>, pub estimate: Option>, @@ -232,8 +232,8 @@ impl Handler for DbExecutor { let list_position = issues .filter(status.eq(IssueStatus::Backlog)) - .select(sql("max(list_position) + 1.0")) - .get_result::(conn) + .select(sql("max(list_position) + 1")) + .get_result::(conn) .map_err(|_| ServiceErrors::DatabaseConnectionLost)?; let form = crate::models::CreateIssueForm { diff --git a/jirs-server/src/models.rs b/jirs-server/src/models.rs index 29cc9cce..297a158c 100644 --- a/jirs-server/src/models.rs +++ b/jirs-server/src/models.rs @@ -50,7 +50,7 @@ pub struct Issue { pub issue_type: IssueType, pub status: IssueStatus, pub priority: IssuePriority, - pub list_position: f64, + pub list_position: i32, pub description: Option, pub description_text: Option, pub estimate: Option, @@ -120,7 +120,7 @@ pub struct CreateIssueForm { pub issue_type: IssueType, pub status: IssueStatus, pub priority: IssuePriority, - pub list_position: f64, + pub list_position: i32, pub description: Option, pub description_text: Option, pub estimate: Option, diff --git a/jirs-server/src/schema.rs b/jirs-server/src/schema.rs index 5b82ccfb..20991a7f 100644 --- a/jirs-server/src/schema.rs +++ b/jirs-server/src/schema.rs @@ -128,10 +128,10 @@ table! { priority -> IssuePriorityType, /// The `list_position` column of the `issues` table. /// - /// Its SQL type is `Float8`. + /// Its SQL type is `Int4`. /// /// (Automatically generated by Diesel.) - list_position -> Float8, + list_position -> Int4, /// The `description` column of the `issues` table. /// /// Its SQL type is `Nullable`. @@ -351,4 +351,11 @@ joinable!(issues -> users (reporter_id)); joinable!(tokens -> users (user_id)); joinable!(users -> projects (project_id)); -allow_tables_to_appear_in_same_query!(comments, issue_assignees, issues, projects, tokens, users,); +allow_tables_to_appear_in_same_query!( + comments, + issue_assignees, + issues, + projects, + tokens, + users, +); diff --git a/jirs-server/src/ws/mod.rs b/jirs-server/src/ws/mod.rs index 609eaa58..4c1bc723 100644 --- a/jirs-server/src/ws/mod.rs +++ b/jirs-server/src/ws/mod.rs @@ -34,10 +34,7 @@ impl WsMessageSender for ws::WebsocketContext { impl WebSocketActor { fn handle_ws_msg(&mut self, msg: WsMsg) -> WsResult { - // use futures::executor::LocalPool; use futures::executor::block_on; - // let mut pool = LocalPool::new(); - // pool.run_until(); if msg != WsMsg::Ping && msg != WsMsg::Pong { info!("(2)incoming message: {:?}", msg); @@ -54,7 +51,7 @@ impl WebSocketActor { WsMsg::ProjectIssuesRequest => block_on(self.load_issues())?, WsMsg::ProjectUsersRequest => block_on(self.load_project_users())?, _ => { - error!("Failed to resolve message"); + error!("No handle for {:?} specified", msg); None } }; @@ -124,36 +121,22 @@ impl WebSocketActor { payload: jirs_data::UpdateIssuePayload, ) -> WsResult { self.current_user()?; - let jirs_data::UpdateIssuePayload { - title, - issue_type, - status, - priority, - list_position, - description, - description_text, - estimate, - time_spent, - time_remaining, - project_id, - user_ids, - } = payload; let m = match self .db .send(UpdateIssue { issue_id, - title, - issue_type, - status, - priority, - list_position, - description, - description_text, - estimate, - time_spent, - time_remaining, - project_id, - user_ids, + title: payload.title, + issue_type: payload.issue_type, + status: payload.status, + priority: payload.priority, + list_position: payload.list_position, + description: payload.description, + description_text: payload.description_text, + estimate: payload.estimate, + time_spent: payload.time_spent, + time_remaining: payload.time_remaining, + project_id: payload.project_id, + user_ids: payload.user_ids, }) .await {