import { Task } from "../types/public-types"; import { BarTask, TaskTypeInternal } from "../types/bar-task"; import { BarMoveAction } from "../types/gantt-task-actions"; export const convertToBarTasks = ( tasks: Task[], dates: Date[], columnWidth: number, rowHeight: number, taskHeight: number, barCornerRadius: number, handleWidth: number, rtl: boolean, barProgressColor: string, barProgressSelectedColor: string, barBackgroundColor: string, barBackgroundSelectedColor: string, projectProgressColor: string, projectProgressSelectedColor: string, projectBackgroundColor: string, projectBackgroundSelectedColor: string, milestoneBackgroundColor: string, milestoneBackgroundSelectedColor: string ) => { let barTasks = tasks.map((t, i) => { return convertToBarTask( t, i, dates, columnWidth, rowHeight, taskHeight, barCornerRadius, handleWidth, rtl, barProgressColor, barProgressSelectedColor, barBackgroundColor, barBackgroundSelectedColor, projectProgressColor, projectProgressSelectedColor, projectBackgroundColor, projectBackgroundSelectedColor, milestoneBackgroundColor, milestoneBackgroundSelectedColor ); }); // set dependencies barTasks = barTasks.map(task => { const dependencies = task.dependencies || []; for (let j = 0; j < dependencies.length; j++) { const dependence = barTasks.findIndex( value => value.id === dependencies[j] ); if (dependence !== -1) barTasks[dependence].barChildren.push(task); } return task; }); return barTasks; }; const convertToBarTask = ( task: Task, index: number, dates: Date[], columnWidth: number, rowHeight: number, taskHeight: number, barCornerRadius: number, handleWidth: number, rtl: boolean, barProgressColor: string, barProgressSelectedColor: string, barBackgroundColor: string, barBackgroundSelectedColor: string, projectProgressColor: string, projectProgressSelectedColor: string, projectBackgroundColor: string, projectBackgroundSelectedColor: string, milestoneBackgroundColor: string, milestoneBackgroundSelectedColor: string ): BarTask => { let barTask: BarTask; switch (task.type) { case "milestone": barTask = convertToMilestone( task, index, dates, columnWidth, rowHeight, taskHeight, barCornerRadius, handleWidth, milestoneBackgroundColor, milestoneBackgroundSelectedColor ); break; case "project": barTask = convertToBar( task, index, dates, columnWidth, rowHeight, taskHeight, barCornerRadius, handleWidth, rtl, projectProgressColor, projectProgressSelectedColor, projectBackgroundColor, projectBackgroundSelectedColor ); break; default: barTask = convertToBar( task, index, dates, columnWidth, rowHeight, taskHeight, barCornerRadius, handleWidth, rtl, barProgressColor, barProgressSelectedColor, barBackgroundColor, barBackgroundSelectedColor ); break; } return barTask; }; const convertToBar = ( task: Task, index: number, dates: Date[], columnWidth: number, rowHeight: number, taskHeight: number, barCornerRadius: number, handleWidth: number, rtl: boolean, barProgressColor: string, barProgressSelectedColor: string, barBackgroundColor: string, barBackgroundSelectedColor: string ): BarTask => { let x1: number; let x2: number; if (rtl) { x2 = taskXCoordinateRTL(task.start, dates, columnWidth); x1 = taskXCoordinateRTL(task.end, dates, columnWidth); } else { x1 = taskXCoordinate(task.start, dates, columnWidth); x2 = taskXCoordinate(task.end, dates, columnWidth); } let typeInternal: TaskTypeInternal = task.type; if (typeInternal === "task" && x2 - x1 < handleWidth * 2) { typeInternal = "smalltask"; x2 = x1 + handleWidth * 2; } const [progressWidth, progressX] = progressWithByParams( x1, x2, task.progress, rtl ); const y = taskYCoordinate(index, rowHeight, taskHeight); const hideChildren = task.type === "project" ? task.hideChildren : undefined; const styles = { backgroundColor: barBackgroundColor, backgroundSelectedColor: barBackgroundSelectedColor, progressColor: barProgressColor, progressSelectedColor: barProgressSelectedColor, ...task.styles, }; return { ...task, typeInternal, x1, x2, y, index, progressX, progressWidth, barCornerRadius, handleWidth, hideChildren, height: taskHeight, barChildren: [], styles, }; }; const convertToMilestone = ( task: Task, index: number, dates: Date[], columnWidth: number, rowHeight: number, taskHeight: number, barCornerRadius: number, handleWidth: number, milestoneBackgroundColor: string, milestoneBackgroundSelectedColor: string ): BarTask => { const x = taskXCoordinate(task.start, dates, columnWidth); const y = taskYCoordinate(index, rowHeight, taskHeight); const x1 = x - taskHeight * 0.5; const x2 = x + taskHeight * 0.5; const rotatedHeight = taskHeight / 1.414; const styles = { backgroundColor: milestoneBackgroundColor, backgroundSelectedColor: milestoneBackgroundSelectedColor, progressColor: "", progressSelectedColor: "", ...task.styles, }; return { ...task, end: task.start, x1, x2, y, index, progressX: 0, progressWidth: 0, barCornerRadius, handleWidth, typeInternal: task.type, progress: 0, height: rotatedHeight, hideChildren: undefined, barChildren: [], styles, }; }; const taskXCoordinate = ( xDate: Date, dates: Date[], columnWidth: number ) => { const index = dates.findIndex(d => d.getTime() >= xDate.getTime()) - 1; const remainderMillis = xDate.getTime() - dates[index].getTime(); const percentOfInterval = remainderMillis / (dates[index + 1 ].getTime() - dates[index].getTime()); const x = index * columnWidth + (percentOfInterval * columnWidth); return x; }; const taskXCoordinateRTL = ( xDate: Date, dates: Date[], columnWidth: number ) => { let x = taskXCoordinate(xDate, dates, columnWidth); x += columnWidth; return x; }; const taskYCoordinate = ( index: number, rowHeight: number, taskHeight: number ) => { const y = index * rowHeight + (rowHeight - taskHeight) / 2; return y; }; export const progressWithByParams = ( taskX1: number, taskX2: number, progress: number, rtl: boolean ) => { const progressWidth = (taskX2 - taskX1) * progress * 0.01; let progressX: number; if (rtl) { progressX = taskX2 - progressWidth; } else { progressX = taskX1; } return [progressWidth, progressX]; }; export const progressByProgressWidth = ( progressWidth: number, barTask: BarTask ) => { const barWidth = barTask.x2 - barTask.x1; const progressPercent = Math.round((progressWidth * 100) / barWidth); if (progressPercent >= 100) return 100; else if (progressPercent <= 0) return 0; else return progressPercent; }; const progressByX = (x: number, task: BarTask) => { if (x >= task.x2) return 100; else if (x <= task.x1) return 0; else { const barWidth = task.x2 - task.x1; const progressPercent = Math.round(((x - task.x1) * 100) / barWidth); return progressPercent; } }; const progressByXRTL = (x: number, task: BarTask) => { if (x >= task.x2) return 0; else if (x <= task.x1) return 100; else { const barWidth = task.x2 - task.x1; const progressPercent = Math.round(((task.x2 - x) * 100) / barWidth); return progressPercent; } }; export const getProgressPoint = ( progressX: number, taskY: number, taskHeight: number ) => { const point = [ progressX - 5, taskY + taskHeight, progressX + 5, taskY + taskHeight, progressX, taskY + taskHeight - 8.66, ]; return point.join(","); }; const startByX = (x: number, xStep: number, task: BarTask) => { if (x >= task.x2 - task.handleWidth * 2) { x = task.x2 - task.handleWidth * 2; } const steps = Math.round((x - task.x1) / xStep); const additionalXValue = steps * xStep; const newX = task.x1 + additionalXValue; return newX; }; const endByX = (x: number, xStep: number, task: BarTask) => { if (x <= task.x1 + task.handleWidth * 2) { x = task.x1 + task.handleWidth * 2; } const steps = Math.round((x - task.x2) / xStep); const additionalXValue = steps * xStep; const newX = task.x2 + additionalXValue; return newX; }; const moveByX = (x: number, xStep: number, task: BarTask) => { const steps = Math.round((x - task.x1) / xStep); const additionalXValue = steps * xStep; const newX1 = task.x1 + additionalXValue; const newX2 = newX1 + task.x2 - task.x1; return [newX1, newX2]; }; const dateByX = ( x: number, taskX: number, taskDate: Date, xStep: number, timeStep: number ) => { let newDate = new Date(((x - taskX) / xStep) * timeStep + taskDate.getTime()); newDate = new Date( newDate.getTime() + (newDate.getTimezoneOffset() - taskDate.getTimezoneOffset()) * 60000 ); return newDate; }; /** * Method handles event in real time(mousemove) and on finish(mouseup) */ export const handleTaskBySVGMouseEvent = ( svgX: number, action: BarMoveAction, selectedTask: BarTask, xStep: number, timeStep: number, initEventX1Delta: number, rtl: boolean ): { isChanged: boolean; changedTask: BarTask } => { let result: { isChanged: boolean; changedTask: BarTask }; switch (selectedTask.type) { case "milestone": result = handleTaskBySVGMouseEventForMilestone( svgX, action, selectedTask, xStep, timeStep, initEventX1Delta ); break; default: result = handleTaskBySVGMouseEventForBar( svgX, action, selectedTask, xStep, timeStep, initEventX1Delta, rtl ); break; } return result; }; const handleTaskBySVGMouseEventForBar = ( svgX: number, action: BarMoveAction, selectedTask: BarTask, xStep: number, timeStep: number, initEventX1Delta: number, rtl: boolean ): { isChanged: boolean; changedTask: BarTask } => { const changedTask: BarTask = { ...selectedTask }; let isChanged = false; switch (action) { case "progress": if (rtl) { changedTask.progress = progressByXRTL(svgX, selectedTask); } else { changedTask.progress = progressByX(svgX, selectedTask); } isChanged = changedTask.progress !== selectedTask.progress; if (isChanged) { const [progressWidth, progressX] = progressWithByParams( changedTask.x1, changedTask.x2, changedTask.progress, rtl ); changedTask.progressWidth = progressWidth; changedTask.progressX = progressX; } break; case "start": { const newX1 = startByX(svgX, xStep, selectedTask); changedTask.x1 = newX1; isChanged = changedTask.x1 !== selectedTask.x1; if (isChanged) { if (rtl) { changedTask.end = dateByX( newX1, selectedTask.x1, selectedTask.end, xStep, timeStep ); } else { changedTask.start = dateByX( newX1, selectedTask.x1, selectedTask.start, xStep, timeStep ); } const [progressWidth, progressX] = progressWithByParams( changedTask.x1, changedTask.x2, changedTask.progress, rtl ); changedTask.progressWidth = progressWidth; changedTask.progressX = progressX; } break; } case "end": { const newX2 = endByX(svgX, xStep, selectedTask); changedTask.x2 = newX2; isChanged = changedTask.x2 !== selectedTask.x2; if (isChanged) { if (rtl) { changedTask.start = dateByX( newX2, selectedTask.x2, selectedTask.start, xStep, timeStep ); } else { changedTask.end = dateByX( newX2, selectedTask.x2, selectedTask.end, xStep, timeStep ); } const [progressWidth, progressX] = progressWithByParams( changedTask.x1, changedTask.x2, changedTask.progress, rtl ); changedTask.progressWidth = progressWidth; changedTask.progressX = progressX; } break; } case "move": { const [newMoveX1, newMoveX2] = moveByX( svgX - initEventX1Delta, xStep, selectedTask ); isChanged = newMoveX1 !== selectedTask.x1; if (isChanged) { changedTask.start = dateByX( newMoveX1, selectedTask.x1, selectedTask.start, xStep, timeStep ); changedTask.end = dateByX( newMoveX2, selectedTask.x2, selectedTask.end, xStep, timeStep ); changedTask.x1 = newMoveX1; changedTask.x2 = newMoveX2; const [progressWidth, progressX] = progressWithByParams( changedTask.x1, changedTask.x2, changedTask.progress, rtl ); changedTask.progressWidth = progressWidth; changedTask.progressX = progressX; } break; } } return { isChanged, changedTask }; }; const handleTaskBySVGMouseEventForMilestone = ( svgX: number, action: BarMoveAction, selectedTask: BarTask, xStep: number, timeStep: number, initEventX1Delta: number ): { isChanged: boolean; changedTask: BarTask } => { const changedTask: BarTask = { ...selectedTask }; let isChanged = false; switch (action) { case "move": { const [newMoveX1, newMoveX2] = moveByX( svgX - initEventX1Delta, xStep, selectedTask ); isChanged = newMoveX1 !== selectedTask.x1; if (isChanged) { changedTask.start = dateByX( newMoveX1, selectedTask.x1, selectedTask.start, xStep, timeStep ); changedTask.end = changedTask.start; changedTask.x1 = newMoveX1; changedTask.x2 = newMoveX2; } break; } } return { isChanged, changedTask }; };