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

View File

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

View File

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

View File

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

View File

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

View File

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