Add dragging

This commit is contained in:
Adrian Wozniak 2020-04-09 08:56:12 +02:00
parent a019d56a59
commit dc3be999be
12 changed files with 125 additions and 51 deletions

View File

@ -86,7 +86,9 @@ pub enum Msg {
// dragging
IssueDragStarted(IssueId),
IssueDragStopped(IssueId),
ExchangePosition(IssueId),
IssueDropZone(IssueStatus),
UnlockDragOver,
// inputs
InputChanged(FieldId, String),

View File

@ -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<Msg> {

View File

@ -118,6 +118,7 @@ pub struct ProjectPage {
pub only_my_filter: bool,
pub recently_updated_filter: bool,
pub dragged_issue_id: Option<IssueId>,
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,

View File

@ -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<Msg>) {
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
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<Msg> {
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<Msg> {
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"],

View File

@ -25,7 +25,9 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
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();

View File

@ -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<String>,
pub description_text: Option<String>,
pub estimate: Option<i32>,
@ -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<String>,
pub description_text: Option<String>,
pub estimate: Option<i32>,
@ -340,7 +340,7 @@ pub struct UpdateIssuePayload {
pub issue_type: Option<IssueType>,
pub status: Option<IssueStatus>,
pub priority: Option<IssuePriority>,
pub list_position: Option<f64>,
pub list_position: Option<i32>,
pub description: Option<Option<String>>,
pub description_text: Option<Option<String>>,
pub estimate: Option<Option<i32>>,

View File

@ -0,0 +1 @@
alter table issues alter column list_position set data type number;

View File

@ -0,0 +1 @@
alter table issues alter column list_position set data type integer;

View File

@ -76,7 +76,7 @@ pub struct UpdateIssue {
pub issue_type: Option<IssueType>,
pub status: Option<IssueStatus>,
pub priority: Option<IssuePriority>,
pub list_position: Option<f64>,
pub list_position: Option<i32>,
pub description: Option<Option<String>>,
pub description_text: Option<Option<String>>,
pub estimate: Option<Option<i32>>,
@ -232,8 +232,8 @@ impl Handler<CreateIssue> for DbExecutor {
let list_position = issues
.filter(status.eq(IssueStatus::Backlog))
.select(sql("max(list_position) + 1.0"))
.get_result::<f64>(conn)
.select(sql("max(list_position) + 1"))
.get_result::<i32>(conn)
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
let form = crate::models::CreateIssueForm {

View File

@ -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<String>,
pub description_text: Option<String>,
pub estimate: Option<i32>,
@ -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<String>,
pub description_text: Option<String>,
pub estimate: Option<i32>,

View File

@ -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<Text>`.
@ -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,
);

View File

@ -34,10 +34,7 @@ impl WsMessageSender for ws::WebsocketContext<WebSocketActor> {
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
{