@ -9,20 +9,9 @@ import { WorkPackageChangeset } from 'core-app/features/work-packages/components
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator' ;
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service' ;
import { I18nService } from 'core-app/core/i18n/i18n.service' ;
import { WeekdayService } from 'core-app/core/days/weekday.service' ;
import { WorkPackageTimelineTableController } from '../container/wp-timeline-container.directive' ;
import { classNameBarLabel , classNameLeftHandle , classNameRightHandle } from './wp-timeline-cell-mouse-handler' ;
import {
classNameFarRightLabel ,
classNameHideOnHover ,
classNameHoverStyle ,
classNameLeftHoverLabel ,
classNameLeftLabel ,
classNameRightContainer ,
classNameRightHoverLabel ,
classNameRightLabel ,
classNameShowOnHover ,
WorkPackageCellLabels ,
} from './wp-timeline-cell' ;
import { WorkPackageCellLabels } from './wp-timeline-cell-labels' ;
import {
calculatePositionValueForDayCount ,
calculatePositionValueForDayCountingPx ,
@ -42,14 +31,25 @@ export interface CellDateMovement {
}
export type LabelPosition = 'left' | 'right' | 'farRight' ;
class TimezoneService {
}
export type MouseDirection = 'left' | 'right' | 'both' | 'create' | 'dragright' ;
export const classNameLeftLabel = 'labelLeft' ;
export const classNameRightContainer = 'containerRight' ;
export const classNameRightLabel = 'labelRight' ;
export const classNameLeftHoverLabel = 'labelHoverLeft' ;
export const classNameRightHoverLabel = 'labelHoverRight' ;
export const classNameHoverStyle = '-label-style' ;
export const classNameFarRightLabel = 'labelFarRight' ;
export const classNameShowOnHover = 'show-on-hover' ;
export const classNameHideOnHover = 'hide-on-hover' ;
export const classNameLeftHandle = 'leftHandle' ;
export const classNameRightHandle = 'rightHandle' ;
export const classNameBarLabel = 'bar-label' ;
export class TimelineCellRenderer {
@InjectField ( ) wpTableTimeline :WorkPackageViewTimelineService ;
@InjectField ( ) TimezoneService :TimezoneService ;
@InjectField ( ) weekdayService :Weekday Service;
@InjectField ( ) schemaCache :SchemaCacheService ;
@ -63,7 +63,7 @@ export class TimelineCellRenderer {
public fieldRenderer :DisplayFieldRenderer = new DisplayFieldRenderer ( this . injector , 'timeline' ) ;
protected dateDisplaysOnMouseMove : { left? :HTMLElement ; right? :HTMLElement } = { } ;
protected mouseDownCursorType :string ;
constructor ( readonly injector :Injector ,
readonly workPackageTimeline :WorkPackageTimelineTableController ) {
@ -76,7 +76,7 @@ export class TimelineCellRenderer {
return 'bar' ;
}
public canMoveDates ( wp :WorkPackageResource ) {
public canMoveDates ( wp :WorkPackageResource ) : boolean {
const schema = this . schemaCache . of ( wp ) ;
return schema . startDate . writable && schema . dueDate . writable && schema . isAttributeEditable ( 'startDate' ) ;
}
@ -89,16 +89,17 @@ export class TimelineCellRenderer {
}
public displayPlaceholderUnderCursor ( ev :MouseEvent , renderInfo :RenderInfo ) : HTMLElement {
const days = Math . floor ( ev . offsetX / renderInfo . viewParams . pixelPerDay ) ;
const [ dateUnderCursor , dayOffset ] = this . cursorDateAndDayOffset ( ev , renderInfo ) ;
const duration = this . displayDurationForDate ( renderInfo , dateUnderCursor ) ;
const width = duration * renderInfo . viewParams . pixelPerDay || 30 ;
const placeholder = document . createElement ( 'div' ) ;
placeholder . style . pointerEvents = 'none' ;
placeholder . style . position = 'absolute' ;
placeholder . style . height = '1em' ;
placeholder . style . width = '30px' ;
placeholder . style . width = ` ${ width } px ` ;
placeholder . style . zIndex = '9999' ;
placeholder . style . left = ` ${ days * renderInfo . viewParams . pixelPerDay } px ` ;
placeholder . style . left = ` ${ dayOffset * renderInfo . viewParams . pixelPerDay } px ` ;
this . applyTypeColor ( renderInfo , placeholder ) ;
return placeholder ;
@ -125,7 +126,7 @@ export class TimelineCellRenderer {
public onDaysMoved ( change :WorkPackageChangeset ,
dayUnderCursor :Moment ,
delta :number ,
direction : 'left' | 'right' | 'both' | 'create' | 'dragright' ) : CellDateMovement {
direction :MouseDirection ) : CellDateMovement {
const initialStartDate = change . pristineResource . startDate ;
const initialDueDate = change . pristineResource . dueDate ;
@ -166,8 +167,7 @@ export class TimelineCellRenderer {
public onMouseDown ( ev :MouseEvent ,
dateForCreate :string | null ,
renderInfo :RenderInfo ,
labels :WorkPackageCellLabels ,
elem :HTMLElement ) : 'left' | 'right' | 'both' | 'dragright' | 'create' {
labels :WorkPackageCellLabels ) : MouseDirection {
// check for active selection mode
if ( renderInfo . viewParams . activeSelectionMode ) {
renderInfo . viewParams . activeSelectionMode ( renderInfo . workPackage ) ;
@ -176,29 +176,34 @@ export class TimelineCellRenderer {
}
const projection = renderInfo . change . projectedResource ;
let direction : 'left' | 'right' | 'both' | 'dragright' ;
let direction :Exclude < MouseDirection , ' create ' > ;
// Update the cursor and maybe set start/due values
if ( jQuery ( ev . target ! ) . hasClass ( classNameLeftHandle ) ) {
// only left
direction = 'left' ;
this . workPackageTimeline . forceCursor ( 'col-resize' ) ;
this . mouseDownCursorType = 'col-resize' ;
if ( projection . startDate === null ) {
projection . startDate = projection . dueDate ;
}
} else if ( jQuery ( ev . target ! ) . hasClass ( classNameRightHandle ) || dateForCreate ) {
// only right
direction = 'right' ;
this . workPackageTimeline . forceCursor ( 'col-resize' ) ;
this . mouseDownCursorType = 'col-resize' ;
} else {
// both
direction = 'both' ;
this . workPackageTimeline . forceCursor ( 'ew-resize' ) ;
this . mouseDownCursorType = 'ew-resize' ;
}
this . workPackageTimeline . forceCursor ( this . mouseDownCursorType ) ;
if ( dateForCreate ) {
const dateUnderCursor = this . cursorDateAndDayOffset ( ev , renderInfo ) [ 0 ] ;
const duration = this . displayDurationForDate ( renderInfo , dateUnderCursor ) - 1 ;
projection . startDate = dateForCreate ;
projection . dueDate = dateForCreate ;
projection . dueDate = moment ( dateForCreate ) . add ( duration , 'days' ) . format ( 'YYYY-MM-DD' ) ;
direction = 'dragright' ;
}
@ -208,6 +213,9 @@ export class TimelineCellRenderer {
}
public onMouseDownEnd ( labels :WorkPackageCellLabels , change :WorkPackageChangeset ) {
// Reset the cursor set by onMouseDown
this . mouseDownCursorType = '' ;
this . workPackageTimeline . forceCursor ( this . mouseDownCursorType ) ;
this . updateLabels ( false , labels , change ) ;
}
@ -254,6 +262,12 @@ export class TimelineCellRenderer {
return true ;
}
public cursorDateAndDayOffset ( ev :MouseEvent , renderInfo :RenderInfo ) : [ Moment , number ] {
const dayOffset = Math . floor ( ev . offsetX / renderInfo . viewParams . pixelPerDay ) ;
const dateUnderCursor = renderInfo . viewParams . dateDisplayStart . clone ( ) . add ( dayOffset , 'days' ) ;
return [ dateUnderCursor , dayOffset ] ;
}
protected checkForActiveSelectionMode ( renderInfo :RenderInfo , element :HTMLElement ) {
if ( renderInfo . viewParams . activeSelectionMode ) {
element . style . backgroundImage = '' ; // required! unable to disable "fade out bar" with css
@ -265,6 +279,41 @@ export class TimelineCellRenderer {
}
}
/ * *
* Takes the date under the cursor and the work package ' s duration .
* It calculates the adjusted duration based on the number of NonWorkingDays
* that fall in the range of the ( date . . date + duration ) .
* @param renderInfo
* @param date where we start the duration calculation from
* @return { number } the NonWorkingDays adjusted duration
* /
protected displayDurationForDate ( renderInfo :RenderInfo , date :Moment ) : number {
const { workPackage } = renderInfo ;
let duration = Number ( moment . duration ( workPackage . duration || 'P1D' ) . asDays ( ) . toFixed ( 0 ) ) ;
if ( workPackage . ignoreNonWorkingDays ) {
return duration ;
}
const { dateDisplayEnd } = renderInfo . viewParams ;
let newDuration = 0 ;
for ( newDuration ; newDuration < duration ; newDuration ++ ) {
const currentDate = date . clone ( ) . add ( newDuration , 'days' ) ;
// Stop adding duration when we reach end of the visible table
if ( currentDate > dateDisplayEnd ) {
break ;
}
// Extend the duration if the currentDate is non-working
if ( this . weekdayService . isNonWorkingDay ( currentDate . toDate ( ) ) ) {
duration += 1 ;
}
}
return newDuration ;
}
getMarginLeftOfLeftSide ( renderInfo :RenderInfo ) : number {
const projection = renderInfo . change . projectedResource ;
@ -401,6 +450,18 @@ export class TimelineCellRenderer {
}
}
cursorOrDatesAreNonWorking ( evOrDates :MouseEvent | Moment [ ] , renderInfo :RenderInfo ) : boolean {
if ( renderInfo . workPackage . ignoreNonWorkingDays ) {
return false ;
}
const dates = ( evOrDates instanceof MouseEvent )
? [ this . cursorDateAndDayOffset ( evOrDates , renderInfo ) [ 0 ] ]
: evOrDates ;
return dates . some ( ( date ) = > this . weekdayService . isNonWorkingDay ( date . toDate ( ) ) ) ;
}
/ * *
* Changes the presentation of the work package .
*
@ -451,6 +512,18 @@ export class TimelineCellRenderer {
childrenDurationBar . appendChild ( childrenDurationHoverContainer ) ;
row . appendChild ( childrenDurationBar ) ;
}
// Check for non-working days and display a not-allowed cursor
// when the startDate, dueDate are non-working days
const { startDate , dueDate } = renderInfo . change . projectedResource ;
const invalidDates = this . cursorOrDatesAreNonWorking ( [ moment ( startDate ) , moment ( dueDate ) ] , renderInfo ) ;
if ( invalidDates ) {
this . workPackageTimeline . forceCursor ( 'not-allowed' ) ;
} else {
// Restore the previous cursor set by onMouseDown
this . workPackageTimeline . forceCursor ( this . mouseDownCursorType ) ;
}
}
protected updateLabels ( activeDragNDrop :boolean ,