add onSelect event & change and delete event fix

This commit is contained in:
unknown 2020-09-09 17:08:16 +03:00
parent 4852c2645d
commit 270131067c
7 changed files with 172 additions and 70 deletions

View File

@ -79,16 +79,20 @@ const App = () => {
},
];
const sleep = (milliseconds: number) => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
};
let onTaskChange = (task: Task) => {
console.log("On date change Id:" + task.id);
};
let onTaskDelete = (task: Task) => {
const conf = window.confirm("Are you sure?");
if (!conf) throw "No del Id:" + task.id;
const conf = window.confirm("Are you sure about " + task.name + " ?");
return conf;
};
let onProgressChange = (task: Task) => {
let onProgressChange = async (task: Task) => {
await sleep(5000);
console.log("On progress change Id:" + task.id);
};
@ -96,6 +100,10 @@ const App = () => {
alert("On Double Click event Id:" + task.id);
};
let onSelect = (task: Task, isSelected: boolean) => {
console.log(task.name + " has " + (isSelected ? "selected" : "unselected"));
};
return (
<div>
<ViewSwitcher
@ -111,6 +119,7 @@ const App = () => {
onTaskDelete={onTaskDelete}
onProgressChange={onProgressChange}
onDoubleClick={onDblClick}
onSelect={onSelect}
listCellWidth={isChecked ? "155px" : ""}
columnWidth={columnWidth}
/>
@ -122,6 +131,7 @@ const App = () => {
onTaskDelete={onTaskDelete}
onProgressChange={onProgressChange}
onDoubleClick={onDblClick}
onSelect={onSelect}
listCellWidth={isChecked ? "155px" : ""}
ganttHeight={300}
columnWidth={columnWidth}

View File

@ -1,6 +1,6 @@
{
"name": "gantt-task-react",
"version": "0.1.8",
"version": "0.1.9",
"description": "Interactive Gantt Chart for React with TypeScript.",
"author": "MaTeMaTuK <maksym.vikarii@gmail.com>",
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",

View File

@ -17,7 +17,7 @@ export type BarProps = {
isDateChangeable: boolean;
isDelete: boolean;
onEventStart: (
event: React.MouseEvent | React.KeyboardEvent,
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
action: GanttContentMoveAction,
selectedTask: BarTask
) => any;
@ -43,9 +43,6 @@ export const Bar: React.FC<BarProps> = ({
return (
<g
className={styles.barWrapper}
onDoubleClick={e => {
onEventStart(e, "dblclick", task);
}}
tabIndex={0}
onKeyDown={e => {
switch (e.key) {
@ -62,8 +59,17 @@ export const Bar: React.FC<BarProps> = ({
onMouseLeave={e => {
onEventStart(e, "mouseleave", task);
}}
onFocus={() => setIsSelected(true)}
onBlur={() => setIsSelected(false)}
onDoubleClick={e => {
onEventStart(e, "dblclick", task);
}}
onFocus={e => {
setIsSelected(true);
onEventStart(e, "select", task);
}}
onBlur={e => {
setIsSelected(false);
onEventStart(e, "unselect", task);
}}
>
<BarDisplay
x={task.x1}

View File

@ -41,6 +41,7 @@ export const Gantt: React.SFC<GanttProps> = ({
onProgressChange,
onDoubleClick,
onTaskDelete,
onSelect,
}) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const [ganttTasks, setGanttTasks] = useState<Task[]>(tasks);
@ -98,8 +99,10 @@ export const Gantt: React.SFC<GanttProps> = ({
};
const handleScrollX = (event: SyntheticEvent<HTMLDivElement>) => {
if (scrollX !== event.currentTarget.scrollLeft)
if (scrollX !== event.currentTarget.scrollLeft && !ignoreScrollEvent) {
setScrollX(event.currentTarget.scrollLeft);
}
setIgnoreScrollEvent(false);
};
/**
@ -195,6 +198,7 @@ export const Gantt: React.SFC<GanttProps> = ({
onProgressChange,
onDoubleClick,
onTaskDelete,
onSelect,
TooltipContent,
};

View File

@ -9,13 +9,15 @@ import {
BarMoveAction,
} from "../../helpers/bar-helper";
import { Tooltip } from "../other/tooltip";
import { isKeyboardEvent } from "../../helpers/other-helper";
import { isKeyboardEvent, isMouseEvent } from "../../helpers/other-helper";
export type GanttContentMoveAction =
| "mouseenter"
| "mouseleave"
| "delete"
| "dblclick"
| "select"
| "unselect"
| BarMoveAction;
export type BarEvent = {
selectedTask?: BarTask;
@ -73,6 +75,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
onProgressChange,
onDoubleClick,
onTaskDelete,
onSelect,
TooltipContent,
}) => {
const point = svg?.current?.createSVGPoint();
@ -80,6 +83,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
action: "",
});
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
const [failedTask, setFailedTask] = useState<BarTask | null>(null);
const [xStep, setXStep] = useState(0);
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
const [isMoving, setIsMoving] = useState(false);
@ -126,48 +130,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
barBackgroundSelectedColor,
]);
/**
* Method is Start point of task change
*/
const handleBarEventStart = async (
event: React.MouseEvent | React.KeyboardEvent,
action: GanttContentMoveAction,
selectedTask: BarTask
) => {
if (isKeyboardEvent(event)) {
if (action === "delete") {
if (onTaskDelete) {
await onTaskDelete(selectedTask);
const newTasks = barTasks.filter(t => t.id !== selectedTask.id);
onTasksChange(newTasks);
}
}
} else if (action === "mouseenter") {
if (!barEvent.action) {
setBarEvent({ action, selectedTask, originalTask: 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()
// on failed task update
useEffect(() => {
if (failedTask) {
const newTasks = barTasks.map(t =>
t.id === failedTask.id ? failedTask : t
);
setInitEventX1Delta(cursor.x - selectedTask.x1);
setBarEvent({ action, selectedTask, originalTask: selectedTask });
} else if (action === "dblclick") {
!!onDoubleClick && onDoubleClick(selectedTask);
} else {
setBarEvent({
action,
selectedTask,
originalTask: selectedTask,
});
onTasksChange(newTasks);
setFailedTask(null);
}
};
}, [failedTask, barTasks]);
useEffect(() => {
const handleMouseMove = async (event: MouseEvent) => {
@ -220,25 +192,46 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
originalTask.end !== changedTask.end ||
originalTask.progress !== changedTask.progress;
if (
(action === "move" || action === "end" || action === "start") &&
onDateChange &&
isNotLikeOriginal
) {
await onDateChange(changedTask);
} else if (onProgressChange && isNotLikeOriginal) {
await onProgressChange(changedTask);
}
// remove listeners
svg.current.removeEventListener("mousemove", handleMouseMove);
svg.current.removeEventListener("mouseup", handleMouseUp);
setBarEvent({ action: "" });
setIsMoving(false);
const newTasks = barTasks.map(t =>
t.id === changedTask.id ? changedTask : t
);
onTasksChange(newTasks);
svg.current.removeEventListener("mousemove", handleMouseMove);
svg.current.removeEventListener("mouseup", handleMouseUp);
setBarEvent({ action: "" });
setIsMoving(false);
// custom operation start
let operationSuccess = true;
if (
(action === "move" || action === "end" || action === "start") &&
onDateChange &&
isNotLikeOriginal
) {
try {
const result = await onDateChange(changedTask);
if (result !== undefined) {
operationSuccess = result;
}
} catch (error) {
operationSuccess = false;
}
} else if (onProgressChange && isNotLikeOriginal) {
try {
const result = await onProgressChange(changedTask);
if (result !== undefined) {
operationSuccess = result;
}
} catch (error) {
operationSuccess = false;
}
}
// If operation is failed - return old state
if (!operationSuccess) {
setFailedTask(originalTask);
}
};
if (
@ -265,6 +258,67 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
isMoving,
]);
/**
* Method is Start point of task change
*/
const handleBarEventStart = async (
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent,
action: GanttContentMoveAction,
selectedTask: BarTask
) => {
// Keyboard events
if (isKeyboardEvent(event)) {
if (action === "delete") {
if (onTaskDelete) {
try {
const result = await onTaskDelete(selectedTask);
if (result !== undefined && result) {
const newTasks = barTasks.filter(t => t.id !== selectedTask.id);
onTasksChange(newTasks);
!!onSelect && onSelect(selectedTask, false);
}
} catch (error) {
console.error("Error on Delete. " + error);
}
}
}
} else if (!isMouseEvent(event)) {
if (action === "select") {
!!onSelect && onSelect(selectedTask, true);
} else if (action === "unselect") {
!!onSelect && onSelect(selectedTask, false);
}
}
// Mouse Events
else if (action === "mouseenter") {
if (!barEvent.action) {
setBarEvent({ action, selectedTask, originalTask: selectedTask });
}
} else if (action === "mouseleave") {
if (barEvent.action === "mouseenter") {
setBarEvent({ action: "" });
}
} else if (action === "dblclick") {
!!onDoubleClick && onDoubleClick(selectedTask);
}
// Change task event start
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, originalTask: selectedTask });
} else {
setBarEvent({
action,
selectedTask,
originalTask: selectedTask,
});
}
};
return (
<g className="content">
<g className="arrows" fill={arrowColor} stroke={arrowColor}>

View File

@ -2,11 +2,17 @@ import { BarTask } from "../types/bar-task";
import { Task } from "../types/public-types";
export function isKeyboardEvent(
event: React.MouseEvent | React.KeyboardEvent
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent
): event is React.KeyboardEvent {
return (event as React.KeyboardEvent).key !== undefined;
}
export function isMouseEvent(
event: React.MouseEvent | React.KeyboardEvent | React.FocusEvent
): event is React.MouseEvent {
return (event as React.MouseEvent).clientX !== undefined;
}
export function isBarTask(task: Task | BarTask): task is BarTask {
return (task as BarTask).x1 !== undefined;
}

View File

@ -30,10 +30,32 @@ export interface EventOption {
* Time step value for date changes.
*/
timeStep?: number;
/**
* Invokes on bar select on unselect.
*/
onSelect?: (task: Task, isSelected: boolean) => void;
/**
* Invokes on bar double click.
*/
onDoubleClick?: (task: Task) => void;
onDateChange?: (task: Task) => void | Promise<any>;
onProgressChange?: (task: Task) => void | Promise<any>;
onTaskDelete?: (task: Task) => void | Promise<any>;
/**
* Invokes on end and start time change. Chart undoes operation if method return false or error.
*/
onDateChange?: (
task: Task
) => void | boolean | Promise<void> | Promise<boolean>;
/**
* Invokes on progress change. Chart undoes operation if method return false or error.
*/
onProgressChange?: (
task: Task
) => void | boolean | Promise<void> | Promise<boolean>;
/**
* Invokes on delete selected task. Chart undoes operation if method return false or error.
*/
onTaskDelete?: (
task: Task
) => void | boolean | Promise<void> | Promise<boolean>;
}
export interface DisplayOption {