event handle rework + firefox support
This commit is contained in:
parent
39f2188c76
commit
7833e05da5
@ -57,8 +57,8 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 26),
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 16),
|
||||||
name: "Release",
|
name: "Release & Eat Burgers",
|
||||||
id: "Task 6",
|
id: "Task 6",
|
||||||
progress: currentDate.getMonth(),
|
progress: currentDate.getMonth(),
|
||||||
dependencies: ["Task 4"],
|
dependencies: ["Task 4"],
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import { BarProgressHandle } from "./bar-progress-handle";
|
|||||||
import { BarDateHandle } from "./bar-date-handle";
|
import { BarDateHandle } from "./bar-date-handle";
|
||||||
import { BarDisplay } from "./bar-display";
|
import { BarDisplay } from "./bar-display";
|
||||||
import { BarTask } from "../../types/bar-task";
|
import { BarTask } from "../../types/bar-task";
|
||||||
import { BarAction } from "../Gantt/gantt-content";
|
|
||||||
import {
|
import {
|
||||||
progressWithByParams,
|
progressWithByParams,
|
||||||
getProgressPoint,
|
getProgressPoint,
|
||||||
} from "../../helpers/bar-helper";
|
} from "../../helpers/bar-helper";
|
||||||
import styles from "./bar.module.css";
|
import styles from "./bar.module.css";
|
||||||
|
import { GanttContentMoveAction } from "../Gantt/gantt-content";
|
||||||
|
|
||||||
export type BarProps = {
|
export type BarProps = {
|
||||||
task: BarTask;
|
task: BarTask;
|
||||||
@ -17,18 +17,12 @@ export type BarProps = {
|
|||||||
onDoubleClick?: (task: Task) => any;
|
onDoubleClick?: (task: Task) => any;
|
||||||
isProgressChangeable: boolean;
|
isProgressChangeable: boolean;
|
||||||
isDateChangeable: boolean;
|
isDateChangeable: boolean;
|
||||||
handleMouseEvents: (
|
isDelete: boolean;
|
||||||
event:
|
onEventStart: (
|
||||||
| React.MouseEvent<SVGPolygonElement, MouseEvent>
|
event: React.MouseEvent | React.KeyboardEvent,
|
||||||
| React.MouseEvent<SVGGElement, MouseEvent>
|
action: GanttContentMoveAction,
|
||||||
| React.MouseEvent<SVGRectElement, MouseEvent>,
|
selectedTask: BarTask
|
||||||
eventType: BarAction,
|
) => any;
|
||||||
task: BarTask
|
|
||||||
) => void;
|
|
||||||
handleButtonSVGEvents: (
|
|
||||||
event: React.KeyboardEvent<SVGGElement>,
|
|
||||||
task: BarTask
|
|
||||||
) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Bar: React.FC<BarProps> = ({
|
export const Bar: React.FC<BarProps> = ({
|
||||||
@ -37,8 +31,8 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
isProgressChangeable,
|
isProgressChangeable,
|
||||||
isDateChangeable,
|
isDateChangeable,
|
||||||
handleMouseEvents,
|
onEventStart,
|
||||||
handleButtonSVGEvents,
|
isDelete,
|
||||||
}) => {
|
}) => {
|
||||||
const [isSelected, setIsSelected] = useState(false);
|
const [isSelected, setIsSelected] = useState(false);
|
||||||
|
|
||||||
@ -57,13 +51,18 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
}}
|
}}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={e => {
|
onKeyDown={e => {
|
||||||
handleButtonSVGEvents(e, task);
|
switch (e.key) {
|
||||||
|
case "Delete": {
|
||||||
|
if (isDelete) onEventStart(e, "delete", task);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onMouseEnter={e => {
|
onMouseEnter={e => {
|
||||||
handleMouseEvents(e, "mouseenter", task);
|
onEventStart(e, "mouseenter", task);
|
||||||
}}
|
}}
|
||||||
onMouseLeave={e => {
|
onMouseLeave={e => {
|
||||||
handleMouseEvents(e, "mouseleave", task);
|
onEventStart(e, "mouseleave", task);
|
||||||
}}
|
}}
|
||||||
onFocus={() => setIsSelected(true)}
|
onFocus={() => setIsSelected(true)}
|
||||||
onBlur={() => setIsSelected(false)}
|
onBlur={() => setIsSelected(false)}
|
||||||
@ -81,7 +80,7 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
styles={task.styles}
|
styles={task.styles}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
isDateChangeable && handleMouseEvents(e, "move", task);
|
isDateChangeable && onEventStart(e, "move", task);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<g className="handleGroup">
|
<g className="handleGroup">
|
||||||
@ -95,7 +94,7 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
height={task.height - 2}
|
height={task.height - 2}
|
||||||
barCornerRadius={task.barCornerRadius}
|
barCornerRadius={task.barCornerRadius}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
handleMouseEvents(e, "start", task);
|
onEventStart(e, "start", task);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* right */}
|
{/* right */}
|
||||||
@ -106,7 +105,7 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
height={task.height - 2}
|
height={task.height - 2}
|
||||||
barCornerRadius={task.barCornerRadius}
|
barCornerRadius={task.barCornerRadius}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
handleMouseEvents(e, "end", task);
|
onEventStart(e, "end", task);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
@ -115,7 +114,7 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
<BarProgressHandle
|
<BarProgressHandle
|
||||||
progressPoint={progressPoint}
|
progressPoint={progressPoint}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
handleMouseEvents(e, "progress", task);
|
onEventStart(e, "progress", task);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Task, EventOption } from "../../types/public-types";
|
import { Task, EventOption } from "../../types/public-types";
|
||||||
import { Bar } from "../Bar/bar";
|
import { Bar } from "../Bar/bar";
|
||||||
import { BarTask } from "../../types/bar-task";
|
import { BarTask } from "../../types/bar-task";
|
||||||
import { Arrow } from "../Other/arrow";
|
import { Arrow } from "../Other/arrow";
|
||||||
import {
|
import {
|
||||||
convertToBarTasks,
|
convertToBarTasks,
|
||||||
progressByX,
|
handleTaskBySVGMouseEvent,
|
||||||
startByX,
|
BarMoveAction,
|
||||||
endByX,
|
|
||||||
moveByX,
|
|
||||||
dateByX,
|
|
||||||
} from "../../helpers/bar-helper";
|
} from "../../helpers/bar-helper";
|
||||||
import { Tooltip } from "../Other/tooltip";
|
import { Tooltip } from "../Other/tooltip";
|
||||||
|
import { isKeyboardEvent } from "../../helpers/other-helper";
|
||||||
|
|
||||||
export interface GanttTask extends Task {
|
export type GanttContentMoveAction =
|
||||||
x1: number;
|
| "mouseenter"
|
||||||
x2: number;
|
| "mouseleave"
|
||||||
y: number;
|
| "delete"
|
||||||
width: number;
|
| BarMoveAction;
|
||||||
height: number;
|
export type BarEvent = {
|
||||||
}
|
selectedTask?: BarTask;
|
||||||
|
action: GanttContentMoveAction;
|
||||||
|
};
|
||||||
export type GanttContentProps = {
|
export type GanttContentProps = {
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
dates: Date[];
|
dates: Date[];
|
||||||
@ -33,8 +33,8 @@ export type GanttContentProps = {
|
|||||||
barBackgroundSelectedColor: string;
|
barBackgroundSelectedColor: string;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
handleWidth: number;
|
handleWidth: number;
|
||||||
svg: React.MutableRefObject<SVGSVGElement | null>;
|
|
||||||
timeStep: number;
|
timeStep: number;
|
||||||
|
svg: React.RefObject<SVGSVGElement>;
|
||||||
arrowColor: string;
|
arrowColor: string;
|
||||||
arrowIndent: number;
|
arrowIndent: number;
|
||||||
fontSize: string;
|
fontSize: string;
|
||||||
@ -46,19 +46,6 @@ export type GanttContentProps = {
|
|||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
} & EventOption;
|
} & EventOption;
|
||||||
|
|
||||||
export type BarAction =
|
|
||||||
| "progress"
|
|
||||||
| "end"
|
|
||||||
| "start"
|
|
||||||
| "move"
|
|
||||||
| "mouseenter"
|
|
||||||
| "mouseleave"
|
|
||||||
| "";
|
|
||||||
|
|
||||||
type BarEvent = {
|
|
||||||
action: BarAction;
|
|
||||||
selectedTask: BarTask | null;
|
|
||||||
};
|
|
||||||
export const GanttContent: React.FC<GanttContentProps> = ({
|
export const GanttContent: React.FC<GanttContentProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
@ -73,26 +60,26 @@ export const GanttContent: React.FC<GanttContentProps> = ({
|
|||||||
headerHeight,
|
headerHeight,
|
||||||
handleWidth,
|
handleWidth,
|
||||||
arrowColor,
|
arrowColor,
|
||||||
svg,
|
|
||||||
timeStep,
|
timeStep,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
arrowIndent,
|
arrowIndent,
|
||||||
|
svg,
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
onTaskDelete,
|
onTaskDelete,
|
||||||
getTooltipContent,
|
getTooltipContent,
|
||||||
}) => {
|
}) => {
|
||||||
|
const point = svg.current?.createSVGPoint();
|
||||||
const [barEvent, setBarEvent] = useState<BarEvent>({
|
const [barEvent, setBarEvent] = useState<BarEvent>({
|
||||||
action: "",
|
action: "",
|
||||||
selectedTask: null,
|
|
||||||
});
|
});
|
||||||
const [isSVGListen, setIsSVGListen] = useState(false);
|
|
||||||
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
||||||
const [xStep, setXStep] = useState(0);
|
const [xStep, setXStep] = useState(0);
|
||||||
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
|
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
|
||||||
|
const [isMoving, setIsMoving] = useState(false);
|
||||||
|
// create xStep
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dateDelta =
|
const dateDelta =
|
||||||
dates[1].getTime() -
|
dates[1].getTime() -
|
||||||
@ -103,8 +90,9 @@ export const GanttContent: React.FC<GanttContentProps> = ({
|
|||||||
if (newXStep !== xStep) {
|
if (newXStep !== xStep) {
|
||||||
setXStep(newXStep);
|
setXStep(newXStep);
|
||||||
}
|
}
|
||||||
}, [tasks, rowHeight, barCornerRadius, columnWidth, dates, timeStep, xStep]);
|
}, [columnWidth, dates, timeStep, xStep]);
|
||||||
|
|
||||||
|
// generate tasks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dateDelta =
|
const dateDelta =
|
||||||
dates[1].getTime() -
|
dates[1].getTime() -
|
||||||
@ -136,258 +124,134 @@ export const GanttContent: React.FC<GanttContentProps> = ({
|
|||||||
barCornerRadius,
|
barCornerRadius,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
dates,
|
dates,
|
||||||
timeStep,
|
|
||||||
barFill,
|
barFill,
|
||||||
handleWidth,
|
handleWidth,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
]);
|
barProgressColor,
|
||||||
|
barProgressSelectedColor,
|
||||||
useEffect(() => {
|
barBackgroundColor,
|
||||||
/**
|
barBackgroundSelectedColor,
|
||||||
* Method handles event in real time(mousemove) and on finish(mouseup)
|
|
||||||
*/
|
|
||||||
const handleMouseSVGChangeEventsSubscribe = async (event: MouseEvent) => {
|
|
||||||
if (!barEvent.selectedTask || !barEvent.action || !svg || !svg.current)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const selectedTask = barEvent.selectedTask;
|
|
||||||
const changedTask = { ...selectedTask } as BarTask;
|
|
||||||
switch (event.type) {
|
|
||||||
// On Event changing
|
|
||||||
case "mousemove": {
|
|
||||||
switch (barEvent.action) {
|
|
||||||
case "progress":
|
|
||||||
changedTask.progress = progressByX(event.offsetX, selectedTask);
|
|
||||||
break;
|
|
||||||
case "start": {
|
|
||||||
const newX1 = startByX(event.offsetX, xStep, selectedTask);
|
|
||||||
changedTask.x1 = newX1;
|
|
||||||
changedTask.start = dateByX(
|
|
||||||
newX1,
|
|
||||||
selectedTask.x1,
|
|
||||||
selectedTask.start,
|
|
||||||
xStep,
|
|
||||||
timeStep
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "end": {
|
|
||||||
const newX2 = endByX(event.offsetX, xStep, selectedTask);
|
|
||||||
changedTask.x2 = newX2;
|
|
||||||
changedTask.end = dateByX(
|
|
||||||
newX2,
|
|
||||||
selectedTask.x2,
|
|
||||||
selectedTask.end,
|
|
||||||
xStep,
|
|
||||||
timeStep
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "move": {
|
|
||||||
const [newMoveX1, newMoveX2] = moveByX(
|
|
||||||
event.offsetX - initEventX1Delta,
|
|
||||||
xStep,
|
|
||||||
selectedTask
|
|
||||||
);
|
|
||||||
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;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update internal state
|
|
||||||
setBarTasks(
|
|
||||||
barTasks.map(t => (t.id === changedTask.id ? changedTask : t))
|
|
||||||
);
|
|
||||||
setBarEvent({ ...barEvent, selectedTask: changedTask });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On finish Event
|
|
||||||
case "mouseup": {
|
|
||||||
let eventForExecution: (
|
|
||||||
task: Task
|
|
||||||
) => void | Promise<void> = () => {};
|
|
||||||
switch (barEvent.action) {
|
|
||||||
case "progress":
|
|
||||||
changedTask.progress = progressByX(event.offsetX, selectedTask);
|
|
||||||
if (onProgressChange) {
|
|
||||||
eventForExecution = onProgressChange;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "start": {
|
|
||||||
const newX1 = startByX(event.offsetX, xStep, selectedTask);
|
|
||||||
changedTask.start = dateByX(
|
|
||||||
newX1,
|
|
||||||
selectedTask.x1,
|
|
||||||
selectedTask.start,
|
|
||||||
xStep,
|
|
||||||
timeStep
|
|
||||||
);
|
|
||||||
if (onDateChange && newX1 !== selectedTask.x1) {
|
|
||||||
eventForExecution = onDateChange;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "end": {
|
|
||||||
const newX2 = endByX(event.offsetX, xStep, selectedTask);
|
|
||||||
changedTask.end = dateByX(
|
|
||||||
newX2,
|
|
||||||
selectedTask.x2,
|
|
||||||
selectedTask.end,
|
|
||||||
xStep,
|
|
||||||
timeStep
|
|
||||||
);
|
|
||||||
|
|
||||||
if (onDateChange && newX2 !== selectedTask.x2) {
|
|
||||||
eventForExecution = onDateChange;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "move": {
|
|
||||||
const [newMoveX1, newMoveX2] = moveByX(
|
|
||||||
event.offsetX - initEventX1Delta,
|
|
||||||
xStep,
|
|
||||||
selectedTask
|
|
||||||
);
|
|
||||||
changedTask.start = dateByX(
|
|
||||||
newMoveX1,
|
|
||||||
selectedTask.x1,
|
|
||||||
selectedTask.start,
|
|
||||||
xStep,
|
|
||||||
timeStep
|
|
||||||
);
|
|
||||||
changedTask.end = dateByX(
|
|
||||||
newMoveX2,
|
|
||||||
selectedTask.x2,
|
|
||||||
selectedTask.end,
|
|
||||||
xStep,
|
|
||||||
timeStep
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
onDateChange &&
|
|
||||||
newMoveX1 !== selectedTask.x1 &&
|
|
||||||
newMoveX2 !== selectedTask.x2
|
|
||||||
) {
|
|
||||||
eventForExecution = onDateChange;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setBarEvent({ action: "", selectedTask: null });
|
|
||||||
setIsSVGListen(false);
|
|
||||||
svg.current.removeEventListener(
|
|
||||||
"mousemove",
|
|
||||||
handleMouseSVGChangeEventsSubscribe
|
|
||||||
);
|
|
||||||
svg.current.removeEventListener(
|
|
||||||
"mouseup",
|
|
||||||
handleMouseSVGChangeEventsSubscribe
|
|
||||||
);
|
|
||||||
|
|
||||||
// If update successful - update Gantt state, otherwise we shell back old Bar state
|
|
||||||
await eventForExecution(changedTask);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
barEvent.selectedTask &&
|
|
||||||
barEvent.action &&
|
|
||||||
!isSVGListen &&
|
|
||||||
svg &&
|
|
||||||
svg.current
|
|
||||||
) {
|
|
||||||
setIsSVGListen(true);
|
|
||||||
svg.current.addEventListener(
|
|
||||||
"mousemove",
|
|
||||||
handleMouseSVGChangeEventsSubscribe
|
|
||||||
);
|
|
||||||
svg.current.addEventListener(
|
|
||||||
"mouseup",
|
|
||||||
handleMouseSVGChangeEventsSubscribe
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
barEvent,
|
|
||||||
isSVGListen,
|
|
||||||
xStep,
|
|
||||||
svg,
|
|
||||||
initEventX1Delta,
|
|
||||||
barTasks,
|
|
||||||
onProgressChange,
|
|
||||||
timeStep,
|
|
||||||
onDateChange,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method is Start point of task change
|
* Method is Start point of task change
|
||||||
* @param event init mouse event
|
|
||||||
* @param eventType
|
|
||||||
* @param task events task
|
|
||||||
*/
|
*/
|
||||||
const handleMouseEvents = (
|
const handleBarEventStart = (
|
||||||
event:
|
event: React.MouseEvent | React.KeyboardEvent,
|
||||||
| React.MouseEvent<SVGPolygonElement, MouseEvent>
|
action: GanttContentMoveAction,
|
||||||
| React.MouseEvent<SVGRectElement, MouseEvent>
|
selectedTask: BarTask
|
||||||
| React.MouseEvent<SVGGElement, MouseEvent>,
|
|
||||||
eventType: BarAction,
|
|
||||||
task: BarTask
|
|
||||||
) => {
|
) => {
|
||||||
switch (event.type) {
|
if (isKeyboardEvent(event)) {
|
||||||
case "mousedown":
|
if (action === "delete") {
|
||||||
setBarEvent({ ...barEvent, selectedTask: task, action: eventType });
|
setBarTasks(barTasks.filter(t => t.id !== barEvent.selectedTask?.id));
|
||||||
setInitEventX1Delta(event.nativeEvent.offsetX - task.x1);
|
|
||||||
event.stopPropagation();
|
|
||||||
break;
|
|
||||||
case "mouseleave":
|
|
||||||
if (!barEvent.action)
|
|
||||||
setBarEvent({ ...barEvent, selectedTask: null, action: "" });
|
|
||||||
break;
|
|
||||||
case "mouseenter":
|
|
||||||
if (!barEvent.selectedTask) {
|
|
||||||
setBarEvent({ ...barEvent, selectedTask: task, action: "" });
|
|
||||||
}
|
}
|
||||||
break;
|
} else if (action === "mouseenter") {
|
||||||
|
if (!barEvent.action) {
|
||||||
|
setBarEvent({ action, selectedTask });
|
||||||
|
}
|
||||||
|
} else if (action === "mouseleave") {
|
||||||
|
if (barEvent.action === "mouseenter") {
|
||||||
|
setBarEvent({ action: "" });
|
||||||
|
}
|
||||||
|
} else if (action === "move") {
|
||||||
|
if (!svg.current || !point) return;
|
||||||
|
point.x = event.clientX;
|
||||||
|
const cursor = point.matrixTransform(
|
||||||
|
svg.current.getScreenCTM()?.inverse()
|
||||||
|
);
|
||||||
|
setInitEventX1Delta(cursor.x - selectedTask.x1);
|
||||||
|
setBarEvent({ action, selectedTask });
|
||||||
|
} else {
|
||||||
|
setBarEvent({
|
||||||
|
action,
|
||||||
|
selectedTask,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
useEffect(() => {
|
||||||
* Method handles Bar keyboard events
|
const handleMouseMove = async (event: MouseEvent) => {
|
||||||
* @param event
|
if (!barEvent.selectedTask || !point || !svg.current) return;
|
||||||
* @param task
|
event.preventDefault();
|
||||||
*/
|
|
||||||
const handleButtonSVGEvents = async (
|
point.x = event.clientX;
|
||||||
event: React.KeyboardEvent<SVGGElement>,
|
const cursor = point.matrixTransform(
|
||||||
task: BarTask
|
svg.current.getScreenCTM()?.inverse()
|
||||||
) => {
|
);
|
||||||
if (task.isDisabled) return;
|
|
||||||
switch (event.key) {
|
const { isChanged, changedTask } = handleTaskBySVGMouseEvent(
|
||||||
case "Delete": {
|
cursor.x,
|
||||||
if (onTaskDelete) {
|
barEvent.action as BarMoveAction,
|
||||||
onTaskDelete(task);
|
barEvent.selectedTask,
|
||||||
}
|
xStep,
|
||||||
break;
|
timeStep,
|
||||||
}
|
initEventX1Delta
|
||||||
|
);
|
||||||
|
if (isChanged) {
|
||||||
|
setBarTasks(
|
||||||
|
barTasks.map(t => (t.id === changedTask.id ? changedTask : t))
|
||||||
|
);
|
||||||
|
setBarEvent({ ...barEvent, selectedTask: changedTask });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = async (event: MouseEvent) => {
|
||||||
|
const { selectedTask, action } = barEvent;
|
||||||
|
if (!selectedTask || !point || !svg.current) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
point.x = event.clientX;
|
||||||
|
const cursor = point.matrixTransform(
|
||||||
|
svg.current.getScreenCTM()?.inverse()
|
||||||
|
);
|
||||||
|
|
||||||
|
const { changedTask } = handleTaskBySVGMouseEvent(
|
||||||
|
cursor.x,
|
||||||
|
action as BarMoveAction,
|
||||||
|
selectedTask,
|
||||||
|
xStep,
|
||||||
|
timeStep,
|
||||||
|
initEventX1Delta
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
(action === "move" || action === "end" || action === "start") &&
|
||||||
|
onDateChange
|
||||||
|
) {
|
||||||
|
onDateChange(changedTask);
|
||||||
|
} else if (onProgressChange) {
|
||||||
|
onProgressChange(changedTask);
|
||||||
|
}
|
||||||
|
svg.current.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
svg.current.removeEventListener("mouseup", handleMouseUp);
|
||||||
|
console.log(`Start: ${changedTask.start} End: ${changedTask.end}`);
|
||||||
|
setBarEvent({ action: "" });
|
||||||
|
setIsMoving(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isMoving &&
|
||||||
|
(barEvent.action === "move" ||
|
||||||
|
barEvent.action === "end" ||
|
||||||
|
barEvent.action === "start" ||
|
||||||
|
barEvent.action === "progress") &&
|
||||||
|
svg.current
|
||||||
|
) {
|
||||||
|
svg.current.addEventListener("mousemove", handleMouseMove);
|
||||||
|
svg.current.addEventListener("mouseup", handleMouseUp);
|
||||||
|
setIsMoving(true);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
barTasks,
|
||||||
|
barEvent,
|
||||||
|
xStep,
|
||||||
|
initEventX1Delta,
|
||||||
|
onProgressChange,
|
||||||
|
timeStep,
|
||||||
|
onDateChange,
|
||||||
|
svg,
|
||||||
|
isMoving,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g className="content">
|
<g className="content">
|
||||||
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
|
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
|
||||||
@ -395,7 +259,7 @@ export const GanttContent: React.FC<GanttContentProps> = ({
|
|||||||
return task.barChildren.map(child => {
|
return task.barChildren.map(child => {
|
||||||
return (
|
return (
|
||||||
<Arrow
|
<Arrow
|
||||||
key={`Arrow from ${task.id} to ${barTasks[child].id}`}
|
key={`Arrow from ${task.id} to ${tasks[child].id}`}
|
||||||
taskFrom={task}
|
taskFrom={task}
|
||||||
taskTo={barTasks[child]}
|
taskTo={barTasks[child]}
|
||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
@ -414,19 +278,17 @@ export const GanttContent: React.FC<GanttContentProps> = ({
|
|||||||
isProgressChangeable={!!onProgressChange && !task.isDisabled}
|
isProgressChangeable={!!onProgressChange && !task.isDisabled}
|
||||||
onDoubleClick={onDoubleClick}
|
onDoubleClick={onDoubleClick}
|
||||||
isDateChangeable={!!onDateChange && !task.isDisabled}
|
isDateChangeable={!!onDateChange && !task.isDisabled}
|
||||||
handleMouseEvents={handleMouseEvents}
|
isDelete={!!onTaskDelete && !task.isDisabled}
|
||||||
handleButtonSVGEvents={handleButtonSVGEvents}
|
onEventStart={handleBarEventStart}
|
||||||
key={task.id}
|
key={task.id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</g>
|
</g>
|
||||||
<g className="toolTip">
|
<g className="toolTip">
|
||||||
{barEvent.selectedTask &&
|
{barEvent.selectedTask && (
|
||||||
barEvent.action !== "end" &&
|
|
||||||
barEvent.action !== "start" && (
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
x={barEvent.selectedTask.x2 + columnWidth + arrowIndent}
|
x={barEvent.selectedTask.x2 + arrowIndent + arrowIndent * 0.5}
|
||||||
y={barEvent.selectedTask.y + rowHeight}
|
y={barEvent.selectedTask.y + rowHeight}
|
||||||
task={barEvent.selectedTask}
|
task={barEvent.selectedTask}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
||||||
const dates = seedDates(startDate, endDate, viewMode);
|
const dates = seedDates(startDate, endDate, viewMode);
|
||||||
const svg = useRef<SVGSVGElement | null>(null);
|
const svg = useRef<SVGSVGElement>(null);
|
||||||
|
|
||||||
const gridProps: GridProps = {
|
const gridProps: GridProps = {
|
||||||
columnWidth,
|
columnWidth,
|
||||||
@ -68,24 +68,23 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
handleWidth,
|
handleWidth,
|
||||||
timeStep,
|
timeStep,
|
||||||
arrowColor,
|
arrowColor,
|
||||||
svg,
|
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
arrowIndent,
|
arrowIndent,
|
||||||
|
svg,
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
onTaskDelete,
|
onTaskDelete,
|
||||||
getTooltipContent,
|
getTooltipContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={columnWidth * dates.length}
|
width={columnWidth * dates.length}
|
||||||
height={headerHeight + rowHeight * tasks.length}
|
height={headerHeight + rowHeight * tasks.length}
|
||||||
ref={svg}
|
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
|
ref={svg}
|
||||||
>
|
>
|
||||||
<Grid {...gridProps} />
|
<Grid {...gridProps} />
|
||||||
<Calendar {...calendarProps} />
|
<Calendar {...calendarProps} />
|
||||||
|
|||||||
@ -68,6 +68,7 @@ export const convertToBarTask = (
|
|||||||
const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
||||||
const x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
const x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
||||||
const y = taskYCoordinate(index, rowHeight, taskHeight, headerHeight);
|
const y = taskYCoordinate(index, rowHeight, taskHeight, headerHeight);
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
backgroundColor: barBackgroundColor,
|
backgroundColor: barBackgroundColor,
|
||||||
backgroundSelectedColor: barBackgroundSelectedColor,
|
backgroundSelectedColor: barBackgroundSelectedColor,
|
||||||
@ -213,3 +214,84 @@ export const dateByX = (
|
|||||||
);
|
);
|
||||||
return newDate;
|
return newDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BarMoveAction = "progress" | "end" | "start" | "move" | "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
) => {
|
||||||
|
const changedTask: BarTask = { ...selectedTask };
|
||||||
|
let isChanged = false;
|
||||||
|
switch (action) {
|
||||||
|
case "progress":
|
||||||
|
changedTask.progress = progressByX(svgX, selectedTask);
|
||||||
|
isChanged = changedTask.progress !== selectedTask.progress;
|
||||||
|
break;
|
||||||
|
case "start": {
|
||||||
|
const newX1 = startByX(svgX, xStep, selectedTask);
|
||||||
|
changedTask.x1 = newX1;
|
||||||
|
isChanged = changedTask.x1 !== selectedTask.x1;
|
||||||
|
if (isChanged) {
|
||||||
|
changedTask.start = dateByX(
|
||||||
|
newX1,
|
||||||
|
selectedTask.x1,
|
||||||
|
selectedTask.start,
|
||||||
|
xStep,
|
||||||
|
timeStep
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "end": {
|
||||||
|
const newX2 = endByX(svgX, xStep, selectedTask);
|
||||||
|
changedTask.x2 = newX2;
|
||||||
|
isChanged = changedTask.x2 !== selectedTask.x2;
|
||||||
|
if (isChanged) {
|
||||||
|
changedTask.end = dateByX(
|
||||||
|
newX2,
|
||||||
|
selectedTask.x2,
|
||||||
|
selectedTask.end,
|
||||||
|
xStep,
|
||||||
|
timeStep
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isChanged, changedTask };
|
||||||
|
};
|
||||||
|
|||||||
5
src/helpers/other-helper.ts
Normal file
5
src/helpers/other-helper.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function isKeyboardEvent(
|
||||||
|
event: React.MouseEvent | React.KeyboardEvent
|
||||||
|
): event is React.KeyboardEvent {
|
||||||
|
return (event as React.KeyboardEvent).key !== undefined;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user