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

pull/5181/head
Roman Roelofsen 8 years ago
parent 511d767704
commit e74858b9d8
  1. 4
      frontend/app/components/work-packages/work-package-cache.service.ts
  2. 30
      frontend/app/components/wp-table/timeline/cell-renderer/timeline-cell-renderer.ts
  3. 4
      frontend/app/components/wp-table/timeline/cell-renderer/timeline-milestone-cell-renderer.ts
  4. 113
      frontend/app/components/wp-table/timeline/wp-timeline-cell-mouse-handler.ts
  5. 24
      frontend/app/components/wp-table/timeline/wp-timeline-cell.ts

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

@ -140,46 +140,50 @@ export class TimelineCellRenderer {
* @return true, if the element should still be displayed. * @return true, if the element should still be displayed.
* false, if the element must be removed from the timeline. * false, if the element must be removed from the timeline.
*/ */
public update(element: HTMLDivElement, wp: WorkPackageResourceInterface, renderInfo: RenderInfo): boolean { public update(timelineCell: HTMLElement, bar: HTMLDivElement, renderInfo: RenderInfo): boolean {
const wp = renderInfo.workPackage;
console.log(wp);
// general settings - bar // general settings - bar
element.style.marginLeft = renderInfo.viewParams.scrollOffsetInPx + "px"; bar.style.marginLeft = renderInfo.viewParams.scrollOffsetInPx + "px";
element.style.backgroundColor = this.typeColor(renderInfo.workPackage); bar.style.backgroundColor = this.typeColor(renderInfo.workPackage);
const viewParams = renderInfo.viewParams; const viewParams = renderInfo.viewParams;
let start = moment(wp.startDate as any); let start = moment(wp.startDate as any);
let due = moment(wp.dueDate as any); let due = moment(wp.dueDate as any);
if (_.isNaN(start.valueOf()) && _.isNaN(due.valueOf())) {
bar.style.visibility = "hidden";
} else {
bar.style.visibility = "visible";
}
// only start date, fade out bar to the right // only start date, fade out bar to the right
if (_.isNaN(due.valueOf()) && !_.isNaN(start.valueOf())) { if (_.isNaN(due.valueOf()) && !_.isNaN(start.valueOf())) {
due = start.clone(); due = start.clone();
element.style.backgroundColor = "inherit"; bar.style.backgroundColor = "inherit";
const color = this.typeColor(renderInfo.workPackage); const color = this.typeColor(renderInfo.workPackage);
element.style.backgroundImage = `linear-gradient(90deg, ${color} 0%, rgba(255,255,255,0) 80%)`; bar.style.backgroundImage = `linear-gradient(90deg, ${color} 0%, rgba(255,255,255,0) 80%)`;
} }
// only due date, fade out bar to the left // only due date, fade out bar to the left
if (_.isNaN(start.valueOf()) && !_.isNaN(due.valueOf())) { if (_.isNaN(start.valueOf()) && !_.isNaN(due.valueOf())) {
start = due.clone(); start = due.clone();
element.style.backgroundColor = "inherit"; bar.style.backgroundColor = "inherit";
const color = this.typeColor(renderInfo.workPackage); const color = this.typeColor(renderInfo.workPackage);
element.style.backgroundImage = `linear-gradient(90deg, rgba(255,255,255,0) 0%, ${color} 100%)`; bar.style.backgroundImage = `linear-gradient(90deg, rgba(255,255,255,0) 0%, ${color} 100%)`;
} }
// offset left // offset left
const offsetStart = start.diff(viewParams.dateDisplayStart, "days"); const offsetStart = start.diff(viewParams.dateDisplayStart, "days");
element.style.left = calculatePositionValueForDayCount(viewParams, offsetStart); bar.style.left = calculatePositionValueForDayCount(viewParams, offsetStart);
// duration // duration
const duration = due.diff(start, "days") + 1; const duration = due.diff(start, "days") + 1;
element.style.width = calculatePositionValueForDayCount(viewParams, duration); bar.style.width = calculatePositionValueForDayCount(viewParams, duration);
// ensure minimum width // ensure minimum width
if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) { if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) {
element.style.minWidth = "10px"; bar.style.minWidth = "10px";
} }
return true; return true;

@ -65,7 +65,9 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
return "both"; return "both";
} }
public update(element:HTMLDivElement, wp: WorkPackageResourceInterface, renderInfo:RenderInfo): boolean { public update(timelineCell: HTMLElement, element: HTMLDivElement, renderInfo: RenderInfo): boolean {
const wp = renderInfo.workPackage;
// abort if no start or due date // abort if no start or due date
if (!wp.date) { if (!wp.date) {
return false; return false;

@ -29,27 +29,46 @@ import {timelineElementCssClass, RenderInfo} from "./wp-timeline";
import {WorkPackageCacheService} from "../../work-packages/work-package-cache.service"; import {WorkPackageCacheService} from "../../work-packages/work-package-cache.service";
import {WorkPackageTimelineTableController} from "./wp-timeline-container.directive"; import {WorkPackageTimelineTableController} from "./wp-timeline-container.directive";
import {TimelineCellRenderer} from "./cell-renderer/timeline-cell-renderer"; import {TimelineCellRenderer} from "./cell-renderer/timeline-cell-renderer";
import {WorkPackageResourceInterface} from "../../api/api-v3/hal-resources/work-package-resource.service";
import IScope = angular.IScope; import IScope = angular.IScope;
import Moment = moment.Moment; import Moment = moment.Moment;
const keyCodeESC = 27;
const classNameBar = "bar"; const classNameBar = "bar";
export const classNameLeftHandle = "leftHandle"; export const classNameLeftHandle = "leftHandle";
export const classNameRightHandle = "rightHandle"; 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, export function registerWorkPackageMouseHandler(this: void,
getRenderInfo: () => RenderInfo,
workPackageTimeline: WorkPackageTimelineTableController, workPackageTimeline: WorkPackageTimelineTableController,
wpCacheService: WorkPackageCacheService, wpCacheService: WorkPackageCacheService,
bar: HTMLElement, cell: HTMLElement,
bar: HTMLDivElement,
renderer: TimelineCellRenderer, renderer: TimelineCellRenderer,
renderInfo: RenderInfo) { renderInfo: RenderInfo) {
let startX: number = null; // also flag to signal active drag'n'drop let startX: number = null; // also flag to signal active drag'n'drop
let dateStates:any; let dateStates:any;
let jBody = jQuery("body"); const jBody = jQuery("body");
const placeholderForEmptyCell = createPlaceholderForEmptyCell();
// handle mouse move on cell
cell.onmousemove = handleMouseMoveOnEmptyCell;
bar.onmousedown = (ev: MouseEvent) => { bar.onmousedown = (ev: MouseEvent) => {
mouseDownFn(ev); workPackageMouseDownFn(ev);
}; };
jBody.on("mouseup", () => { jBody.on("mouseup", () => {
@ -80,12 +99,12 @@ export function registerWorkPackageMouseHandler(this: void,
function keyPressFn(ev: JQueryEventObject) { function keyPressFn(ev: JQueryEventObject) {
const kev: KeyboardEvent = ev as any; const kev: KeyboardEvent = ev as any;
if (kev.keyCode === 27) { // ESC if (kev.keyCode === keyCodeESC) {
deactivate(true); deactivate(true);
} }
} }
function mouseDownFn(ev: MouseEvent) { function workPackageMouseDownFn(ev: MouseEvent) {
ev.preventDefault(); ev.preventDefault();
workPackageTimeline.disableViewParamsCalculation = true; workPackageTimeline.disableViewParamsCalculation = true;
@ -98,6 +117,80 @@ export function registerWorkPackageMouseHandler(this: void,
jBody.on("keyup", keyPressFn); jBody.on("keyup", keyPressFn);
} }
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) {
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";
cell.appendChild(placeholderForEmptyCell);
cell.onmouseleave = () => {
placeholderForEmptyCell.remove();
};
// create logic
cell.onmousedown = (ev) => {
console.log("create - mouse down");
placeholderForEmptyCell.remove();
ev.preventDefault();
bar.style.pointerEvents = "none";
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";
renderer.update(cell, bar, renderInfo);
cell.onmousemove = handleMouseMoveOnEmptyCell;
}
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");
renderer.update(cell, bar, renderInfo);
};
cell.onmouseleave = () => {
cancel(true);
};
cell.onmouseup = () => {
cancel(false);
saveWorkPackage(renderInfo.workPackage);
};
jBody.on("keyup.create", (ev) => {
const kev: KeyboardEvent = ev as any;
if (kev.keyCode === keyCodeESC) {
cancel(true);
}
});
};
}
function deactivate(cancelled: boolean) { function deactivate(cancelled: boolean) {
workPackageTimeline.disableViewParamsCalculation = false; workPackageTimeline.disableViewParamsCalculation = false;
@ -122,10 +215,16 @@ export function registerWorkPackageMouseHandler(this: void,
} }
// Persist the changes // Persist the changes
wpCacheService.saveIfChanged(renderInfo.workPackage) saveWorkPackage(renderInfo.workPackage);
}
function saveWorkPackage(workPackage: WorkPackageResourceInterface) {
console.log("saveWorkPackage()");
wpCacheService.saveIfChanged(workPackage)
.catch(() => { .catch(() => {
// Reset the changes on error // Reset the changes on error
renderer.onCancel(renderInfo.workPackage); renderer.onCancel(workPackage);
}) })
.finally(() => { .finally(() => {
workPackageTimeline.refreshView(); workPackageTimeline.refreshView();

@ -33,6 +33,7 @@ import {registerWorkPackageMouseHandler} from "./wp-timeline-cell-mouse-handler"
import {TimelineMilestoneCellRenderer} from "./cell-renderer/timeline-milestone-cell-renderer"; import {TimelineMilestoneCellRenderer} from "./cell-renderer/timeline-milestone-cell-renderer";
import {TimelineCellRenderer} from "./cell-renderer/timeline-cell-renderer"; import {TimelineCellRenderer} from "./cell-renderer/timeline-cell-renderer";
import {Subscription} from "rxjs"; import {Subscription} from "rxjs";
import {WorkPackageResourceInterface} from "../../api/api-v3/hal-resources/work-package-resource.service";
import IScope = angular.IScope; import IScope = angular.IScope;
import Moment = moment.Moment; import Moment = moment.Moment;
@ -45,7 +46,10 @@ export class WorkPackageTimelineCell {
private subscription: Subscription; private subscription: Subscription;
private element: HTMLDivElement = null; private latestRenderInfo: RenderInfo;
private wpElement: HTMLDivElement = null;
private elementShape: string = null; private elementShape: string = null;
constructor(private workPackageTimeline: WorkPackageTimelineTableController, constructor(private workPackageTimeline: WorkPackageTimelineTableController,
@ -70,11 +74,11 @@ export class WorkPackageTimelineCell {
private clear() { private clear() {
this.timelineCell.innerHTML = ""; this.timelineCell.innerHTML = "";
this.element = null; this.wpElement = null;
} }
private lazyInit(renderer: TimelineCellRenderer, renderInfo: RenderInfo) { private lazyInit(renderer: TimelineCellRenderer, renderInfo: RenderInfo) {
const wasRendered = this.element !== null && this.element.parentNode; const wasRendered = this.wpElement !== null && this.wpElement.parentNode;
// If already rendered with correct shape, ignore // If already rendered with correct shape, ignore
if (wasRendered && (this.elementShape === renderer.type)) { if (wasRendered && (this.elementShape === renderer.type)) {
@ -87,15 +91,17 @@ export class WorkPackageTimelineCell {
} }
// Render the given element // Render the given element
this.element = renderer.render(renderInfo); this.wpElement = renderer.render(renderInfo);
this.elementShape = renderer.type; this.elementShape = renderer.type;
// Register the element // Register the element
this.timelineCell.appendChild(this.element); this.timelineCell.appendChild(this.wpElement);
registerWorkPackageMouseHandler( registerWorkPackageMouseHandler(
() => this.latestRenderInfo,
this.workPackageTimeline, this.workPackageTimeline,
this.wpCacheService, this.wpCacheService,
this.element, this.timelineCell,
this.wpElement,
renderer, renderer,
renderInfo); renderInfo);
@ -130,14 +136,14 @@ export class WorkPackageTimelineCell {
} }
private updateView(renderInfo: RenderInfo) { private updateView(renderInfo: RenderInfo) {
const wp = renderInfo.workPackage; this.latestRenderInfo = renderInfo;
const renderer = this.cellRenderer(wp); const renderer = this.cellRenderer(renderInfo.workPackage);
// Render initial element if necessary // Render initial element if necessary
this.lazyInit(renderer, renderInfo); this.lazyInit(renderer, renderInfo);
// Render the upgrade from renderInfo // Render the upgrade from renderInfo
const shouldBeDisplayed = renderer.update(this.element, wp, renderInfo); const shouldBeDisplayed = renderer.update(this.timelineCell, this.wpElement, renderInfo);
if (!shouldBeDisplayed) { if (!shouldBeDisplayed) {
this.clear(); this.clear();
} }

Loading…
Cancel
Save