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) {
console.log("saveIfChangedaaaaaaaaaaa");
if (!(workPackage.dirty || workPackage.isNew)) {
return this.$q.when(workPackage);
}
console.log("saveIfChangedbbbbbbbbbbbbb");
const deferred = this.$q.defer();
workPackage.save()
.then(() => {

@ -140,46 +140,50 @@ export class TimelineCellRenderer {
* @return true, if the element should still be displayed.
* false, if the element must be removed from the timeline.
*/
public update(element: HTMLDivElement, wp: WorkPackageResourceInterface, renderInfo: RenderInfo): boolean {
console.log(wp);
public update(timelineCell: HTMLElement, bar: HTMLDivElement, renderInfo: RenderInfo): boolean {
const wp = renderInfo.workPackage;
// general settings - bar
element.style.marginLeft = renderInfo.viewParams.scrollOffsetInPx + "px";
element.style.backgroundColor = this.typeColor(renderInfo.workPackage);
bar.style.marginLeft = renderInfo.viewParams.scrollOffsetInPx + "px";
bar.style.backgroundColor = this.typeColor(renderInfo.workPackage);
const viewParams = renderInfo.viewParams;
let start = moment(wp.startDate 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
if (_.isNaN(due.valueOf()) && !_.isNaN(start.valueOf())) {
due = start.clone();
element.style.backgroundColor = "inherit";
bar.style.backgroundColor = "inherit";
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
if (_.isNaN(start.valueOf()) && !_.isNaN(due.valueOf())) {
start = due.clone();
element.style.backgroundColor = "inherit";
bar.style.backgroundColor = "inherit";
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
const offsetStart = start.diff(viewParams.dateDisplayStart, "days");
element.style.left = calculatePositionValueForDayCount(viewParams, offsetStart);
bar.style.left = calculatePositionValueForDayCount(viewParams, offsetStart);
// duration
const duration = due.diff(start, "days") + 1;
element.style.width = calculatePositionValueForDayCount(viewParams, duration);
bar.style.width = calculatePositionValueForDayCount(viewParams, duration);
// ensure minimum width
if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) {
element.style.minWidth = "10px";
bar.style.minWidth = "10px";
}
return true;

@ -65,7 +65,9 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
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
if (!wp.date) {
return false;

@ -29,27 +29,46 @@ import {timelineElementCssClass, RenderInfo} from "./wp-timeline";
import {WorkPackageCacheService} from "../../work-packages/work-package-cache.service";
import {WorkPackageTimelineTableController} from "./wp-timeline-container.directive";
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 Moment = moment.Moment;
const keyCodeESC = 27;
const classNameBar = "bar";
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,
wpCacheService: WorkPackageCacheService,
bar: HTMLElement,
cell: HTMLElement,
bar: HTMLDivElement,
renderer: TimelineCellRenderer,
renderInfo: RenderInfo) {
let startX: number = null; // also flag to signal active drag'n'drop
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) => {
mouseDownFn(ev);
workPackageMouseDownFn(ev);
};
jBody.on("mouseup", () => {
@ -80,12 +99,12 @@ export function registerWorkPackageMouseHandler(this: void,
function keyPressFn(ev: JQueryEventObject) {
const kev: KeyboardEvent = ev as any;
if (kev.keyCode === 27) { // ESC
if (kev.keyCode === keyCodeESC) {
deactivate(true);
}
}
function mouseDownFn(ev: MouseEvent) {
function workPackageMouseDownFn(ev: MouseEvent) {
ev.preventDefault();
workPackageTimeline.disableViewParamsCalculation = true;
@ -98,6 +117,80 @@ export function registerWorkPackageMouseHandler(this: void,
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) {
workPackageTimeline.disableViewParamsCalculation = false;
@ -122,10 +215,16 @@ export function registerWorkPackageMouseHandler(this: void,
}
// Persist the changes
wpCacheService.saveIfChanged(renderInfo.workPackage)
saveWorkPackage(renderInfo.workPackage);
}
function saveWorkPackage(workPackage: WorkPackageResourceInterface) {
console.log("saveWorkPackage()");
wpCacheService.saveIfChanged(workPackage)
.catch(() => {
// Reset the changes on error
renderer.onCancel(renderInfo.workPackage);
renderer.onCancel(workPackage);
})
.finally(() => {
workPackageTimeline.refreshView();

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

Loading…
Cancel
Save