WIP Timeline: create start/due values with drag'n'drop

pull/5181/head
Roman Roelofsen 8 years ago
parent e74858b9d8
commit 41780ae1e2
  1. 5
      frontend/app/components/work-packages/work-package-cache.service.ts
  2. 42
      frontend/app/components/wp-table/timeline/cell-renderer/timeline-cell-renderer.ts
  3. 70
      frontend/app/components/wp-table/timeline/cell-renderer/timeline-milestone-cell-renderer.ts
  4. 89
      frontend/app/components/wp-table/timeline/wp-timeline-cell-mouse-handler.ts

@ -83,13 +83,14 @@ export class WorkPackageCacheService {
}
saveIfChanged(workPackage) {
console.log("saveIfChangedaaaaaaaaaaa");
console.log("saveIfChanged");
if (!(workPackage.dirty || workPackage.isNew)) {
console.log(" - aborted");
return this.$q.when(workPackage);
}
console.log("saveIfChangedbbbbbbbbbbbbb");
console.log(" - pass");
const deferred = this.$q.defer();
workPackage.save()

@ -22,6 +22,27 @@ export class TimelineCellRenderer {
return '#8CD1E8';
}
public isEmpty(wp: WorkPackageResourceInterface) {
const start = moment(wp.startDate as any);
const due = moment(wp.dueDate as any);
const noStartAndDueValues = _.isNaN(start.valueOf()) && _.isNaN(due.valueOf());
return noStartAndDueValues;
}
public displayPlaceholderUnderCursor(ev: MouseEvent, renderInfo: RenderInfo): HTMLElement {
const days = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const placeholder = document.createElement("div");
placeholder.style.pointerEvents = "none";
placeholder.style.backgroundColor = "#DDDDDD";
placeholder.style.position = "absolute";
placeholder.style.height = "1em";
placeholder.style.width = "30px";
placeholder.style.left = (days * renderInfo.viewParams.pixelPerDay) + "px";
return placeholder;
}
/**
* Assign changed dates to the work package.
* For generic work packages, assigns start and due date.
@ -47,8 +68,9 @@ export class TimelineCellRenderer {
* depending on which initial date was set.
*/
public onDaysMoved(wp: WorkPackageResourceInterface,
dayUnderCursor: Moment,
delta: number,
direction: "left" | "right" | "both"): CellDateMovement {
direction: "left" | "right" | "both" | "create" | "dragright"): CellDateMovement {
const initialStartDate = wp.$pristine['startDate'];
const initialDueDate = wp.$pristine['dueDate'];
@ -66,6 +88,8 @@ export class TimelineCellRenderer {
if (initialDueDate) {
dates.dueDate = moment(initialDueDate).add(delta, "days");
}
} else if (direction === "dragright") {
dates.dueDate = moment(wp.startDate).clone().add(delta, "days");
}
// avoid negative "overdrag" if only start or due are changed
@ -80,10 +104,14 @@ export class TimelineCellRenderer {
return dates;
}
public onMouseDown(ev: MouseEvent, renderInfo: RenderInfo, elem: HTMLElement): "left" | "right" | "both" {
public onMouseDown(ev: MouseEvent,
dateForCreate: string|null,
renderInfo: RenderInfo,
elem: HTMLElement): "left" | "right" | "both" | "dragright" | "create" {
renderInfo.workPackage.storePristine('startDate');
renderInfo.workPackage.storePristine('dueDate');
let direction: "left" | "right" | "both";
let direction: "left" | "right" | "both" | "create" | "dragright";
// Update the cursor and maybe set start/due values
if (jQuery(ev.target).hasClass(classNameLeftHandle)) {
@ -93,7 +121,7 @@ export class TimelineCellRenderer {
if (renderInfo.workPackage.startDate === null) {
renderInfo.workPackage.startDate = renderInfo.workPackage.dueDate;
}
} else if (jQuery(ev.target).hasClass(classNameRightHandle)) {
} else if (jQuery(ev.target).hasClass(classNameRightHandle) || dateForCreate) {
// only right
direction = "right";
this.forceCursor('e-resize');
@ -108,6 +136,12 @@ export class TimelineCellRenderer {
this.dateDisplaysOnMouseMove = [null, null];
if (dateForCreate) {
renderInfo.workPackage.startDate = dateForCreate;
renderInfo.workPackage.dueDate = dateForCreate;
direction = "dragright";
}
if (!jQuery(ev.target).hasClass(classNameRightHandle) && renderInfo.workPackage.startDate) {
// create left date label
const leftInfo = document.createElement("div");

@ -10,20 +10,48 @@ interface CellMilestoneMovement {
export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
public get type():string {
public get type(): string {
return 'milestone';
}
public get fallbackColor():string {
public get fallbackColor(): string {
return '#C0392B';
}
public isEmpty(wp: WorkPackageResourceInterface) {
const date = moment(wp.date as any);
const noDateValue = _.isNaN(date.valueOf());
return noDateValue;
}
public displayPlaceholderUnderCursor(ev: MouseEvent, renderInfo: RenderInfo): HTMLElement {
const days = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const placeholder = document.createElement("div");
placeholder.className = "timeline-element milestone";
placeholder.style.pointerEvents = "none";
placeholder.style.position = "absolute";
placeholder.style.height = "1em";
placeholder.style.width = "1em";
placeholder.style.left = (days * renderInfo.viewParams.pixelPerDay) + "px";
const diamond = document.createElement("div");
diamond.className = "diamond";
diamond.style.backgroundColor = "#DDDDDD";
diamond.style.left = "0.5em";
diamond.style.height = "1em";
diamond.style.width = "1em";
placeholder.appendChild(diamond);
return placeholder;
}
/**
* Assign changed dates to the work package.
* For generic work packages, assigns start and due date.
*
*/
public assignDateValues(wp: WorkPackageResourceInterface, dates:CellMilestoneMovement) {
public assignDateValues(wp: WorkPackageResourceInterface, dates: CellMilestoneMovement) {
this.assignDate(wp, 'date', dates.date);
this.updateMilestoneMovedLabel(dates.date);
@ -39,9 +67,13 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
/**
* Handle movement by <delta> days of milestone.
*/
public onDaysMoved(wp:WorkPackageResourceInterface, delta:number, direction: "left" | "right" | "both") {
public onDaysMoved(wp: WorkPackageResourceInterface,
dayUnderCursor: Moment,
delta: number,
direction: "left" | "right" | "both" | "create" | "dragright") {
const initialDate = wp.$pristine['date'];
let dates:CellMilestoneMovement = {};
let dates: CellMilestoneMovement = {};
if (initialDate) {
dates.date = moment(initialDate).add(delta, "days");
@ -50,9 +82,19 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
return dates;
}
public onMouseDown(ev: MouseEvent, renderInfo: RenderInfo, elem: HTMLElement): "left" | "right" | "both" {
this.forceCursor('ew-resize');
public onMouseDown(ev: MouseEvent,
dateForCreate: string|null,
renderInfo: RenderInfo,
elem: HTMLElement): "left" | "right" | "both" | "create" | "dragright" {
let direction: "left" | "right" | "both" | "create" | "dragright" = "both";
renderInfo.workPackage.storePristine('date');
this.forceCursor('ew-resize');
if (dateForCreate) {
renderInfo.workPackage.date = dateForCreate;
direction = "create";
}
// create date label
const dateInfo = document.createElement("div");
@ -62,11 +104,13 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
this.updateMilestoneMovedLabel(moment(renderInfo.workPackage.date));
return "both";
return direction;
}
public update(timelineCell: HTMLElement, element: HTMLDivElement, renderInfo: RenderInfo): boolean {
const wp = renderInfo.workPackage;
const viewParams = renderInfo.viewParams;
const date = moment(wp.date as any);
// abort if no start or due date
if (!wp.date) {
@ -75,16 +119,12 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
const diamond = jQuery(".diamond", element)[0];
element.style.marginLeft = renderInfo.viewParams.scrollOffsetInPx + "px";
element.style.marginLeft = viewParams.scrollOffsetInPx + "px";
element.style.width = '1em';
element.style.height = '1em';
diamond.style.width = '1em';
diamond.style.height = '1em';
diamond.style.backgroundColor = this.typeColor(renderInfo.workPackage);
const viewParams = renderInfo.viewParams;
const date = moment(wp.date as any);
diamond.style.backgroundColor = this.typeColor(wp);
// offset left
const offsetStart = date.diff(viewParams.dateDisplayStart, "days");
@ -97,7 +137,7 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
* Render a milestone element, a single day event with no resize, but
* move functionality.
*/
public render(renderInfo: RenderInfo):HTMLDivElement {
public render(renderInfo: RenderInfo): HTMLDivElement {
const element = document.createElement("div");
element.className = timelineElementCssClass + " " + this.type;

@ -40,16 +40,6 @@ export const classNameLeftHandle = "leftHandle";
export const classNameRightHandle = "rightHandle";
function createPlaceholderForEmptyCell() {
const placeholder = document.createElement("div");
placeholder.style.pointerEvents = "none";
placeholder.style.backgroundColor = "#DDDDDD";
placeholder.style.position = "absolute";
placeholder.style.height = "1em";
placeholder.style.width = "30px";
return placeholder;
}
export function registerWorkPackageMouseHandler(this: void,
getRenderInfo: () => RenderInfo,
workPackageTimeline: WorkPackageTimelineTableController,
@ -61,8 +51,8 @@ export function registerWorkPackageMouseHandler(this: void,
let startX: number = null; // also flag to signal active drag'n'drop
let dateStates:any;
let placeholderForEmptyCell: HTMLElement;
const jBody = jQuery("body");
const placeholderForEmptyCell = createPlaceholderForEmptyCell();
// handle mouse move on cell
cell.onmousemove = handleMouseMoveOnEmptyCell;
@ -86,13 +76,16 @@ export function registerWorkPackageMouseHandler(this: void,
wpCacheService.updateWorkPackage(wp);
}
function createMouseMoveFn(direction: "left" | "right" | "both") {
function createMouseMoveFn(direction: "left" | "right" | "both" | "create" | "dragright") {
return (ev: JQueryEventObject) => {
const mev: MouseEvent = ev as any;
const distance = Math.floor((mev.clientX - startX) / renderInfo.viewParams.pixelPerDay);
const days = distance < 0 ? distance + 1 : distance;
dateStates = renderer.onDaysMoved(renderInfo.workPackage, days, direction);
const offsetDayCurrent = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const dayUnderCursor = renderInfo.viewParams.dateDisplayStart.clone().add(offsetDayCurrent, "days");
dateStates = renderer.onDaysMoved(renderInfo.workPackage, dayUnderCursor, days, direction);
applyDateValues(dateStates);
}
}
@ -111,7 +104,7 @@ export function registerWorkPackageMouseHandler(this: void,
startX = ev.clientX;
// Determine what attributes of the work package should be changed
const direction = renderer.onMouseDown(ev, renderInfo, bar);
const direction = renderer.onMouseDown(ev, null, renderInfo, bar);
jBody.on("mousemove", createMouseMoveFn(direction));
jBody.on("keyup", keyPressFn);
@ -120,84 +113,74 @@ export function registerWorkPackageMouseHandler(this: void,
function handleMouseMoveOnEmptyCell(ev: MouseEvent) {
const renderInfo = getRenderInfo();
const wp = renderInfo.workPackage;
const start = moment(wp.startDate as any);
const due = moment(wp.dueDate as any);
const noStartDueValues = _.isNaN(start.valueOf()) && _.isNaN(due.valueOf());
if (!noStartDueValues) {
if (!renderer.isEmpty(wp)) {
return;
}
// placeholder logic
const days = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
// const dayUnderCursor = renderInfo.viewParams.dateDisplayStart.clone().add(days, "days");
placeholderForEmptyCell.style.left = (days * renderInfo.viewParams.pixelPerDay) + "px";
placeholderForEmptyCell && placeholderForEmptyCell.remove();
placeholderForEmptyCell = renderer.displayPlaceholderUnderCursor(ev, renderInfo);
cell.appendChild(placeholderForEmptyCell);
// abort if mouse leaves cell
cell.onmouseleave = () => {
placeholderForEmptyCell.remove();
};
// create logic
cell.onmousedown = (ev) => {
console.log("create - mouse down");
placeholderForEmptyCell.remove();
ev.preventDefault();
bar.style.pointerEvents = "none";
ev.preventDefault();
const days = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const clickStart = renderInfo.viewParams.dateDisplayStart.clone().add(days, "days");
renderInfo.workPackage.startDate = clickStart.format("YYYY-MM-DD");
renderInfo.workPackage.dueDate = clickStart.format("YYYY-MM-DD");
renderer.update(cell, bar, renderInfo);
function cancel(resetStartDueValues: boolean) {
console.log("create - cancel()");
jBody.off(".create");
if (resetStartDueValues) {
renderInfo.workPackage.startDate = null;
renderInfo.workPackage.dueDate = null;
}
bar.style.pointerEvents = "auto";
const offsetDayStart = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const clickStart = renderInfo.viewParams.dateDisplayStart.clone().add(offsetDayStart, "days");
const dateForCreate = clickStart.format("YYYY-MM-DD");
const mouseDownType = renderer.onMouseDown(ev, dateForCreate, renderInfo, bar);
renderer.update(cell, bar, renderInfo);
cell.onmousemove = handleMouseMoveOnEmptyCell;
if (mouseDownType === "create") {
deactivate(false);
return;
}
cell.onmousemove = (ev) => {
console.log("create - mouse move");
const days = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const currentEnd = renderInfo.viewParams.dateDisplayStart.clone().add(days, "days");
renderInfo.workPackage.dueDate = currentEnd.format("YYYY-MM-DD");
const offsetDayCurrent = Math.floor(ev.offsetX / renderInfo.viewParams.pixelPerDay);
const dayUnderCursor = renderInfo.viewParams.dateDisplayStart.clone().add(offsetDayCurrent, "days");
const widthInDays = offsetDayCurrent - offsetDayStart;
const moved = renderer.onDaysMoved(wp, dayUnderCursor, widthInDays, mouseDownType);
renderer.assignDateValues(wp, moved);
renderer.update(cell, bar, renderInfo);
};
cell.onmouseleave = () => {
cancel(true);
deactivate(true);
};
cell.onmouseup = () => {
cancel(false);
saveWorkPackage(renderInfo.workPackage);
deactivate(false);
};
jBody.on("keyup.create", (ev) => {
jBody.on("keyup", (ev) => {
const kev: KeyboardEvent = ev as any;
if (kev.keyCode === keyCodeESC) {
cancel(true);
deactivate(true);
}
});
};
}
function deactivate(cancelled: boolean) {
workPackageTimeline.disableViewParamsCalculation = false;
if (startX == null) {
return;
}
// if (startX == null) {
// return;
// }
cell.onmousemove = handleMouseMoveOnEmptyCell;
bar.style.pointerEvents = "auto";
jBody.off("mousemove");
jBody.off("keyup");
jQuery(".hascontextmenu").css("cursor", "context-menu");
@ -219,8 +202,6 @@ export function registerWorkPackageMouseHandler(this: void,
}
function saveWorkPackage(workPackage: WorkPackageResourceInterface) {
console.log("saveWorkPackage()");
wpCacheService.saveIfChanged(workPackage)
.catch(() => {
// Reset the changes on error

Loading…
Cancel
Save