Merge pull request #8569 from opf/feature/32881-children-duration-bar-on-gantt-charts

Show children duration clamp on gantt charts
pull/8603/head
ulferts 4 years ago committed by GitHub
commit bf280643e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      config/locales/js-en.yml
  2. 2
      frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts
  3. 13
      frontend/src/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts
  4. 86
      frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts
  5. 4
      frontend/src/app/components/wp-table/wp-table-hover-sync.ts
  6. 25
      frontend/src/global_styles/content/work_packages/timelines/_timelines.sass
  7. 1
      frontend/src/global_styles/content/work_packages/timelines/elements/_bar.sass
  8. 1
      frontend/src/global_styles/layout/work_packages/_table.sass
  9. 3
      frontend/src/global_styles/openproject/_variables.scss

@ -450,6 +450,7 @@ en:
label_total_amount: "Total: %{amount}"
label_updated_on: "updated on"
label_value_derived_from_children: "(value derived from children)"
label_children_derived_duration: "Work package's children derived duration"
label_warning: "Warning"
label_work_package: "Work package"
label_work_package_parent: "Parent work package"

@ -23,7 +23,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
@InjectField() wpTableHierarchies:WorkPackageViewHierarchiesService;
// Remember which rows were already rendered
private rendered:{ [workPackageId:string]:boolean } = {};
readonly rendered:{ [workPackageId:string]:boolean } = {};
// Remember additional parents inserted that are not part of the results table
private additionalParents:{ [workPackageId:string]:WorkPackageResource } = {};

@ -74,7 +74,18 @@ export class HierarchyTransformer {
// Toggle the root style
jQuery(`.${hierarchyRootClass(wpId)} .wp-table--hierarchy-indicator`).toggleClass(indicatorCollapsedClass, isCollapsed);
// Get all affected rows
// Get parent row and mark/unmark it as collapsed
const hierarchyRoot = document.querySelector(`.wp-timeline-cell.__hierarchy-root-${wpId}`);
if (hierarchyRoot) {
if (isCollapsed) {
hierarchyRoot.classList.add(`__hierarchy-root-collapsed`);
} else {
hierarchyRoot.classList.remove(`__hierarchy-root-collapsed`);
}
}
// Get all affected children rows
const affected = jQuery(`.${hierarchyGroupClass(wpId)}`);
// Hide/Show the descendants.

@ -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);
}
}

@ -91,7 +91,9 @@ export class WpTableHoverSync {
const wpId = this.extractWorkPackageId(hovered!);
const tableRow:JQuery = this.tableAndTimeline.find('tr.wp-row-' + wpId).first();
const timelineRow:JQuery = this.tableAndTimeline.find('div.wp-row-' + wpId).first();
const timelineRow:JQuery = this.tableAndTimeline.find('div.wp-row-' + wpId).length ?
this.tableAndTimeline.find('div.wp-row-' + wpId).first() :
this.tableAndTimeline.find('div.wp-ancestor-row-' + wpId).first();
requestAnimationFrame(() => {
this.removeAllHoverClasses();

@ -79,3 +79,28 @@
// Add margin to timeline when inline-create link is showing in table
.wp-table-timeline--body.-inline-create-mirror
margin-bottom: var(--table-timeline--row-height)
.children-duration-bar
position: absolute
height: 10px
top: 1.7em
border: 1px solid
background: none
border-bottom: none
.-duration-overflow
border-color: var(--warn)
.children-duration-hover-container
display: none
margin-left: -1px
margin-right: -1px
background-color: #d3d3d361
.wp-timeline-cell.row-hovered .children-duration-hover-container
display: block
.wp-timeline-cell.__hierarchy-root-collapsed .children-duration-hover-container
display: none

@ -3,7 +3,6 @@
height: 1em
border-radius: 2px
float: left
z-index: 0
.bar-label
overflow: hidden

@ -63,7 +63,6 @@
// Use important to override the background styles from __hl classes
// That also have to be important (cf. 30863)
background: #d1ebfb85 !important
z-index: 1
// Left part of the split view
// == flex container for (table|timeline)

@ -195,7 +195,7 @@
--generic-table--header-height: 45px;
--generic-table--footer-height: 34px;
--timeline--header-border-color: #aaaaaa;
--timeline--grid-color: #dddddd;
--timeline--grid-color: #dddddd5e;
--timeline--separator: 3px solid #E7E7E7;
--timeline--type-fallback-color: rgba(150, 150, 150, 0.8);
--table-timeline--row-height: 40px;
@ -208,4 +208,5 @@
--project-status-green: #00D12D;
--grid-background-color: #F3F6F8;
--work-package-details--tab-height: 40px;
--warn:#C92A2A;
}

Loading…
Cancel
Save