|
|
|
@ -32,6 +32,7 @@ import {WorkPackageViewTimelineService} from "core-app/modules/work_packages/rou |
|
|
|
|
import {WorkPackageChangeset} from "core-components/wp-edit/work-package-changeset"; |
|
|
|
|
import {InjectField} from "core-app/helpers/angular/inject-field.decorator"; |
|
|
|
|
import {SchemaCacheService} from "core-components/schemas/schema-cache.service"; |
|
|
|
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
|
|
|
|
|
|
|
|
|
export interface CellDateMovement { |
|
|
|
|
// Target values to move work package to
|
|
|
|
@ -47,6 +48,13 @@ export class TimelineCellRenderer { |
|
|
|
|
@InjectField() wpTableTimeline:WorkPackageViewTimelineService; |
|
|
|
|
@InjectField() TimezoneService:TimezoneService; |
|
|
|
|
@InjectField() schemaCache:SchemaCacheService; |
|
|
|
|
@InjectField() readonly I18n:I18nService; |
|
|
|
|
|
|
|
|
|
public text = { |
|
|
|
|
label_children_derived_duration: this.I18n.t('js.label_children_derived_duration') |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
public ganttChartRowHeight:number; |
|
|
|
|
|
|
|
|
|
public fieldRenderer:DisplayFieldRenderer = new DisplayFieldRenderer(this.injector, 'timeline'); |
|
|
|
|
|
|
|
|
@ -54,6 +62,9 @@ export class TimelineCellRenderer { |
|
|
|
|
|
|
|
|
|
constructor(readonly injector:Injector, |
|
|
|
|
readonly workPackageTimeline:WorkPackageTimelineTableController) { |
|
|
|
|
this.ganttChartRowHeight = +getComputedStyle(document.documentElement) |
|
|
|
|
.getPropertyValue('--table-timeline--row-height') |
|
|
|
|
.replace('px', ''); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public get type():string { |
|
|
|
@ -205,8 +216,6 @@ export class TimelineCellRenderer { |
|
|
|
|
public update(element:HTMLDivElement, labels:WorkPackageCellLabels|null, renderInfo:RenderInfo):boolean { |
|
|
|
|
const change = renderInfo.change; |
|
|
|
|
const bar = element.querySelector(`.${timelineBackgroundElementClass}`) as HTMLElement; |
|
|
|
|
|
|
|
|
|
const viewParams = renderInfo.viewParams; |
|
|
|
|
let start = moment(change.projectedResource.startDate); |
|
|
|
|
let due = moment(change.projectedResource.dueDate); |
|
|
|
|
|
|
|
|
@ -229,19 +238,7 @@ export class TimelineCellRenderer { |
|
|
|
|
bar.style.backgroundImage = `linear-gradient(90deg, #F1F1F1 0%, rgba(255,255,255,0) 80%)`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// offset left
|
|
|
|
|
const offsetStart = start.diff(viewParams.dateDisplayStart, 'days'); |
|
|
|
|
element.style.left = calculatePositionValueForDayCount(viewParams, offsetStart); |
|
|
|
|
|
|
|
|
|
// duration
|
|
|
|
|
const duration = due.diff(start, 'days') + 1; |
|
|
|
|
element.style.width = calculatePositionValueForDayCount(viewParams, duration); |
|
|
|
|
|
|
|
|
|
// ensure minimum width
|
|
|
|
|
if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) { |
|
|
|
|
const minWidth = _.max([renderInfo.viewParams.pixelPerDay, 2]); |
|
|
|
|
element.style.minWidth = minWidth + 'px'; |
|
|
|
|
} |
|
|
|
|
this.setElementPositionAndSize(element, renderInfo, start, due); |
|
|
|
|
|
|
|
|
|
// Update labels if any
|
|
|
|
|
if (labels) { |
|
|
|
@ -370,9 +367,9 @@ export class TimelineCellRenderer { |
|
|
|
|
let type = wp.type; |
|
|
|
|
let selectionMode = renderInfo.viewParams.activeSelectionMode; |
|
|
|
|
|
|
|
|
|
// Don't apply the class in selection mode or for parents (clamps)
|
|
|
|
|
// Don't apply the class in selection mode
|
|
|
|
|
const id = type.id; |
|
|
|
|
if (selectionMode || this.isParentWithVisibleChildren(wp)) { |
|
|
|
|
if (selectionMode) { |
|
|
|
|
bg.classList.remove(Highlighting.backgroundClass('type', id!)); |
|
|
|
|
} else { |
|
|
|
|
bg.classList.add(Highlighting.backgroundClass('type', id!)); |
|
|
|
@ -385,14 +382,34 @@ export class TimelineCellRenderer { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setElementPositionAndSize(element:HTMLElement, renderInfo:RenderInfo, start:moment.Moment, due:moment.Moment) { |
|
|
|
|
const viewParams = renderInfo.viewParams; |
|
|
|
|
// offset left
|
|
|
|
|
const offsetStart = start.diff(viewParams.dateDisplayStart, 'days'); |
|
|
|
|
element.style.left = calculatePositionValueForDayCount(viewParams, offsetStart); |
|
|
|
|
|
|
|
|
|
// duration
|
|
|
|
|
const duration = due.diff(start, 'days') + 1; |
|
|
|
|
element.style.width = calculatePositionValueForDayCount(viewParams, duration); |
|
|
|
|
|
|
|
|
|
// ensure minimum width
|
|
|
|
|
if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) { |
|
|
|
|
const minWidth = _.max([renderInfo.viewParams.pixelPerDay, 2]); |
|
|
|
|
element.style.minWidth = minWidth + 'px'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Changes the presentation of the work package. |
|
|
|
|
* |
|
|
|
|
* Known cases: |
|
|
|
|
* 1. Display a clamp if this work package is a parent element |
|
|
|
|
* and display a box wrapping all the visible children when the |
|
|
|
|
* parent is hovered |
|
|
|
|
*/ |
|
|
|
|
checkForSpecialDisplaySituations(renderInfo:RenderInfo, bar:HTMLElement) { |
|
|
|
|
const wp = renderInfo.workPackage; |
|
|
|
|
const row = bar.parentElement!.parentElement!; |
|
|
|
|
let selectionMode = renderInfo.viewParams.activeSelectionMode; |
|
|
|
|
|
|
|
|
|
// Cannot edit the work package if it has children
|
|
|
|
@ -403,13 +420,34 @@ export class TimelineCellRenderer { |
|
|
|
|
bar.classList.remove('-readonly'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Display the parent as clamp-style when it has children in the table
|
|
|
|
|
if (this.isParentWithVisibleChildren(wp)) { |
|
|
|
|
bar.classList.add('-clamp-style'); |
|
|
|
|
bar.style.borderStyle = 'solid'; |
|
|
|
|
bar.style.borderWidth = '2px'; |
|
|
|
|
bar.style.borderBottom = 'none'; |
|
|
|
|
bar.style.background = 'none'; |
|
|
|
|
// Display the children's duration clamp
|
|
|
|
|
if (wp.derivedStartDate && wp.derivedDueDate) { |
|
|
|
|
const derivedStartDate = moment(wp.derivedStartDate); |
|
|
|
|
const derivedDueDate = moment(wp.derivedDueDate); |
|
|
|
|
const startDate = moment(renderInfo.change.projectedResource.startDate); |
|
|
|
|
const dueDate = moment(renderInfo.change.projectedResource.dueDate); |
|
|
|
|
const previousChildrenDurationBar = row.querySelector('.children-duration-bar'); |
|
|
|
|
const childrenDurationBar = document.createElement('div'); |
|
|
|
|
const childrenDurationHoverContainer = document.createElement('div'); |
|
|
|
|
const visibleChildren = document.querySelectorAll(`.wp-timeline-cell.__hierarchy-group-${wp.id}:not([class*='__collapsed-group'])`).length || 0; |
|
|
|
|
|
|
|
|
|
childrenDurationBar.classList.add('children-duration-bar', '-clamp-style'); |
|
|
|
|
childrenDurationBar.title = this.text.label_children_derived_duration; |
|
|
|
|
childrenDurationHoverContainer.classList.add('children-duration-hover-container'); |
|
|
|
|
childrenDurationHoverContainer.style.height = this.ganttChartRowHeight * visibleChildren + 10 + 'px'; |
|
|
|
|
|
|
|
|
|
if (derivedStartDate.isBefore(startDate) || derivedDueDate.isAfter(dueDate)) { |
|
|
|
|
childrenDurationBar.classList.add('-duration-overflow'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.setElementPositionAndSize(childrenDurationBar, renderInfo, derivedStartDate, derivedDueDate); |
|
|
|
|
|
|
|
|
|
if (previousChildrenDurationBar) { |
|
|
|
|
previousChildrenDurationBar.remove(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
childrenDurationBar.appendChild(childrenDurationHoverContainer); |
|
|
|
|
row!.appendChild(childrenDurationBar); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|