593 lines
14 KiB
TypeScript
593 lines
14 KiB
TypeScript
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 };
|
|
};
|