OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/frontend/app/components/wp-table/timeline/cells/timeline-milestone-cell-ren...

246 lines
8.0 KiB

import * as moment from 'moment';
import {$injectNow} from '../../../angular/angular-injector-bridge.functions';
import {WorkPackageResourceInterface} from '../../../api/api-v3/hal-resources/work-package-resource.service';
import {calculatePositionValueForDayCountingPx, RenderInfo, timelineElementCssClass} from '../wp-timeline';
import {TimelineCellRenderer} from './timeline-cell-renderer';
import {
classNameFarRightLabel,
classNameLeftLabel, classNameRightContainer, classNameRightLabel, classNameShowOnHover,
WorkPackageCellLabels
} from './wp-timeline-cell';
import Moment = moment.Moment;
import {WorkPackageChangeset} from '../../../wp-edit-form/work-package-changeset';
interface CellMilestoneMovement {
// Target value to move milestone to
date?:moment.Moment;
}
export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
public get type():string {
return 'milestone';
}
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.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(changeset:WorkPackageChangeset,
labels:WorkPackageCellLabels,
dates:CellMilestoneMovement) {
this.assignDate(changeset, 'date', dates.date!);
this.updateLabels(true, labels, changeset);
}
/**
* Handle movement by <delta> days of milestone.
*/
public onDaysMoved(wp:WorkPackageResourceInterface,
dayUnderCursor:Moment,
delta:number,
direction:'left' | 'right' | 'both' | 'create' | 'dragright') {
const initialDate = wp.date;
let dates: CellMilestoneMovement = {};
if (initialDate) {
dates.date = moment(initialDate).add(delta, 'days');
}
return dates;
}
public onMouseDown(ev:MouseEvent,
dateForCreate:string | null,
renderInfo:RenderInfo,
labels:WorkPackageCellLabels,
elem:HTMLElement):'left' | 'right' | 'both' | 'create' | 'dragright' {
// check for active selection mode
if (renderInfo.viewParams.activeSelectionMode) {
renderInfo.viewParams.activeSelectionMode(renderInfo.workPackage);
ev.preventDefault();
return 'both'; // irrelevant
}
let direction: "left" | "right" | "both" | "create" | "dragright" = "both";
renderInfo.changeset!.startEditing('date');
this.forceCursor('ew-resize');
if (dateForCreate) {
renderInfo.workPackage.date = dateForCreate;
direction = 'create';
return direction;
}
this.updateLabels(true, labels, renderInfo.changeset!);
return direction;
}
public update(element: HTMLDivElement, renderInfo: RenderInfo): boolean {
const changeset = renderInfo.changeset || new WorkPackageChangeset(renderInfo.workPackage);
const viewParams = renderInfo.viewParams;
const date = moment(changeset.value('dueDate'));
// abort if no date
if (!date) {
return false;
}
const diamond = jQuery('.diamond', element)[0];
element.style.width = 15 + "px";
element.style.height = 15 + "px";
diamond.style.width = 15 + "px";
diamond.style.height = 15 + "px";
diamond.style.marginLeft = -(15 / 2) + (renderInfo.viewParams.pixelPerDay / 2) + "px";
diamond.style.backgroundColor = this.typeColor(renderInfo.workPackage);
// offset left
const offsetStart = date.diff(viewParams.dateDisplayStart, 'days');
element.style.left = calculatePositionValueForDayCountingPx(viewParams, offsetStart) + 'px';
this.checkForActiveSelectionMode(renderInfo, diamond);
return true;
}
getMarginLeftOfLeftSide(renderInfo:RenderInfo):number {
const wp = renderInfo.workPackage;
let start = moment(wp.date as any);
const offsetStart = start.diff(renderInfo.viewParams.dateDisplayStart, 'days');
return calculatePositionValueForDayCountingPx(renderInfo.viewParams, offsetStart);
}
getMarginLeftOfRightSide(ri:RenderInfo):number {
return this.getMarginLeftOfLeftSide(ri) + ri.viewParams.pixelPerDay;
}
getPaddingLeftForIncomingRelationLines(renderInfo:RenderInfo):number {
return (renderInfo.viewParams.pixelPerDay / 2) - 1;
}
getPaddingRightForOutgoingRelationLines(renderInfo:RenderInfo):number {
return (15 / 2);
}
/**
* Render a milestone element, a single day event with no resize, but
* move functionality.
*/
public render(renderInfo:RenderInfo):HTMLDivElement {
const element = document.createElement('div');
element.className = timelineElementCssClass + ' ' + this.type;
const diamond = document.createElement('div');
diamond.className = 'diamond';
element.appendChild(diamond);
return element;
}
createAndAddLabels(renderInfo:RenderInfo, element:HTMLElement):WorkPackageCellLabels {
// create left label
const labelLeft = document.createElement('div');
labelLeft.classList.add(classNameLeftLabel);
labelLeft.classList.add(classNameShowOnHover);
element.appendChild(labelLeft);
// create right container
const containerRight = document.createElement('div');
containerRight.classList.add(classNameRightContainer);
element.appendChild(containerRight);
// create right label
const labelRight = document.createElement('div');
labelRight.classList.add(classNameRightLabel);
labelRight.classList.add(classNameShowOnHover);
containerRight.appendChild(labelRight);
// create far right label
const labelFarRight = document.createElement('div');
labelFarRight.classList.add(classNameFarRightLabel);
labelFarRight.classList.add(classNameShowOnHover);
containerRight.appendChild(labelFarRight);
const labels = new WorkPackageCellLabels(null, labelLeft, labelRight, labelFarRight);
this.updateLabels(false, labels, renderInfo.changeset!);
return labels;
}
protected updateLabels(activeDragNDrop:boolean,
labels:WorkPackageCellLabels,
changeset:WorkPackageChangeset) {
if (!this.TimezoneService) {
this.TimezoneService = $injectNow('TimezoneService');
}
const subject:string = changeset.value('subject');
const dateStr = changeset.value('date');
const date:Moment | null = dateStr ? moment(dateStr) : null;
if (!activeDragNDrop) {
// normal display
if (labels.labelRight && date) {
labels.labelRight.textContent = this.TimezoneService.formattedDate(date);
}
if (labels.labelFarRight) {
labels.labelFarRight.textContent = subject;
}
} else {
// active drag'n'drop
if (labels.labelRight && date) {
labels.labelRight.textContent = this.TimezoneService.formattedDate(date);
}
}
if (labels.labelLeft) {
if (_.isEmpty(labels.labelLeft.textContent)) {
labels.labelLeft.classList.remove('not-empty');
} else {
labels.labelLeft.classList.add('not-empty');
}
}
if (labels.labelRight) {
if (_.isEmpty(labels.labelRight.textContent)) {
labels.labelRight.classList.remove('not-empty');
} else {
labels.labelRight.classList.add('not-empty');
}
}
}
}