Skip to content

Commit

Permalink
Add task dependency contextual palette
Browse files Browse the repository at this point in the history
This commit also merge DateExtremity and RelationMoveTarget types.
DateExtremity is kept.

Signed-off-by: Laurent Fasani <laurent.fasani@obeo.fr>
  • Loading branch information
lfasani committed Jun 18, 2024
1 parent 8b6d9a2 commit f1942bf
Show file tree
Hide file tree
Showing 17 changed files with 300 additions and 94 deletions.
2 changes: 1 addition & 1 deletion src/components/gantt/default-round-date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const defaultRoundDate = (
viewMode: ViewMode,
dateExtremity: DateExtremity
) => {
if (dateExtremity == "start") {
if (dateExtremity == "startOfTask") {
return defaultRoundStartDate(date, viewMode);
} else {
return defaultRoundEndDate(date, viewMode);
Expand Down
2 changes: 2 additions & 0 deletions src/components/gantt/gantt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export const Gantt: React.FC<GanttProps> = ({
TaskListTable = TaskListTableDefault,
TooltipContent = StandardTooltipContent,
ContextualPalette,
TaskDependencyContextualPalette,
authorizedRelations = [
"startToStart",
"startToEnd",
Expand Down Expand Up @@ -1802,6 +1803,7 @@ export const Gantt: React.FC<GanttProps> = ({
timeStep,
visibleTasksMirror,
ContextualPalette,
TaskDependencyContextualPalette,
}),
[
additionalLeftSpace,
Expand Down
25 changes: 19 additions & 6 deletions src/components/gantt/task-gantt-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
FixPosition,
GlobalRowIndexToTaskMap,
RelationKind,
RelationMoveTarget,
DateExtremity,
Task,
TaskContextualPaletteProps,
TaskCoordinates,
TaskOrEmpty,
TaskToHasDependencyWarningMap,
TaskDependencyContextualPaletteProps,
} from "../../types/public-types";
import { Arrow } from "../other/arrow";
import { RelationLine } from "../other/relation-line";
Expand Down Expand Up @@ -48,7 +49,7 @@ export type TaskGanttContentProps = {
ganttRelationEvent: GanttRelationEvent | null;
getTaskCoordinates: (task: Task) => TaskCoordinates;
getTaskGlobalIndexByRef: (task: Task) => number;
handleBarRelationStart: (target: RelationMoveTarget, task: Task) => void;
handleBarRelationStart: (target: DateExtremity, task: Task) => void;
handleDeleteTasks: (task: TaskOrEmpty[]) => void;
handleFixDependency: (task: Task, delta: number) => void;
handleTaskDragStart: (
Expand All @@ -59,6 +60,13 @@ export type TaskGanttContentProps = {
) => void;
isShowDependencyWarnings: boolean;
mapGlobalRowIndexToTask: GlobalRowIndexToTaskMap;
onArrowClick?: (
taskFrom: Task,
extremityFrom: DateExtremity,
taskTo: Task,
extremityTo: DateExtremity,
event: React.MouseEvent<SVGElement>
) => void;
onArrowDoubleClick: (taskFrom: Task, taskTo: Task) => void;
onClick?: (task: Task, event: React.MouseEvent<SVGElement>) => void;
onDoubleClick?: (task: Task) => void;
Expand All @@ -73,6 +81,7 @@ export type TaskGanttContentProps = {
taskHeight: number;
taskHalfHeight: number;
ContextualPalette?: React.FC<TaskContextualPaletteProps>;
TaskDependencyContextualPalette?: React.FC<TaskDependencyContextualPaletteProps>;
};

export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
Expand Down Expand Up @@ -102,6 +111,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
isShowDependencyWarnings,
mapGlobalRowIndexToTask,
onArrowDoubleClick,
onArrowClick,
onDoubleClick,
onClick,
renderedRowIndexes,
Expand Down Expand Up @@ -299,6 +309,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({

arrowsRes.push(
<svg
className="ArrowClassName"
x={containerX + (additionalLeftSpace || 0)}
y={containerY}
width={containerWidth}
Expand All @@ -309,12 +320,12 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
colorStyles={colorStyles}
distances={distances}
taskFrom={source}
targetFrom={sourceTarget}
extremityFrom={sourceTarget}
fromX1={fromX1 - containerX}
fromX2={fromX2 - containerX}
fromY={innerFromY}
taskTo={task}
targetTo={ownTarget}
extremityTo={ownTarget}
toX1={taskX1 - containerX}
toX2={taskX2 - containerX}
toY={innerToY}
Expand All @@ -325,6 +336,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
isCritical={isCritical}
rtl={rtl}
onArrowDoubleClick={onArrowDoubleClick}
onArrowClick={onArrowClick}
handleFixDependency={handleFixDependency}
/>
</svg>
Expand Down Expand Up @@ -393,12 +405,12 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
colorStyles={colorStyles}
distances={distances}
taskFrom={task}
targetFrom={ownTarget}
extremityFrom={ownTarget}
fromX1={taskX1 - containerX}
fromX2={taskX2 - containerX}
fromY={innerFromY}
taskTo={dependent}
targetTo={dependentTarget}
extremityTo={dependentTarget}
toX1={toX1 - containerX}
toX2={toX2 - containerX}
toY={innerToY}
Expand All @@ -409,6 +421,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
isCritical={isCritical}
rtl={rtl}
onArrowDoubleClick={onArrowDoubleClick}
onArrowClick={onArrowClick}
handleFixDependency={handleFixDependency}
/>
</svg>
Expand Down
83 changes: 80 additions & 3 deletions src/components/gantt/task-gantt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
TaskContextualPaletteProps,
Task,
Distances,
DateExtremity,
TaskDependencyContextualPaletteProps,
} from "../../types/public-types";
import ClickAwayListener from "@material-ui/core/ClickAwayListener/ClickAwayListener";

Expand Down Expand Up @@ -76,6 +78,63 @@ const TaskGanttInner: React.FC<TaskGanttProps> = ({
]
);

const [arrowAnchorEl, setArrowAnchorEl] = React.useState<null | SVGElement>(
null
);
const [selectedDependency, setSelectedDependency] = React.useState<{
taskFrom: Task;
extremityFrom: DateExtremity;
taskTo: Task;
extremityTo: DateExtremity;
}>(null);
const isArrowContextualPaletteOpened = Boolean(arrowAnchorEl);
const onClickArrow: (
taskFrom: Task,
extremityFrom: DateExtremity,
taskTo: Task,
extremityTo: DateExtremity,
event: React.MouseEvent<SVGElement>
) => void = (taskFrom, extremityFrom, taskTo, extremityTo, event) => {
setArrowAnchorEl(event.currentTarget);
setSelectedDependency({ taskFrom, extremityFrom, taskTo, extremityTo });
};

const onCloseArrowContextualPalette = () => {
setArrowAnchorEl(null);
};

let arrowContextualPalette:
| React.FunctionComponentElement<TaskDependencyContextualPaletteProps>
| undefined = undefined;
if (barProps.TaskDependencyContextualPalette && selectedDependency) {
arrowContextualPalette = React.createElement(
barProps.TaskDependencyContextualPalette,
{
taskFrom: selectedDependency.taskFrom,
extremityFrom: selectedDependency.extremityFrom,
taskTo: selectedDependency.taskTo,
extremityTo: selectedDependency.extremityTo,
onClosePalette: onCloseArrowContextualPalette,
}
);
} else {
arrowContextualPalette = <div></div>;
}

const onArrowClickAway = (e: React.MouseEvent<Document, MouseEvent>) => {
const svgElement = e.target as SVGElement;
if (svgElement) {
const keepPalette =
svgElement.ownerSVGElement?.classList.contains("ArrowClassName");
// In a better world the contextual palette should be defined in TaskItem component but ClickAwayListener and Popper uses div that are not displayed in svg
// So in order to let the palette open when clicking on another task, this checks if the user clicked on another task
if (!keepPalette) {
setArrowAnchorEl(null);
setSelectedDependency(null);
}
}
};

// Manage the contextual palette
const [anchorEl, setAnchorEl] = React.useState<null | SVGElement>(null);
const [selectedTask, setSelectedTask] = React.useState<Task>(null);
Expand All @@ -99,7 +158,7 @@ const TaskGanttInner: React.FC<TaskGanttProps> = ({
if (barProps.ContextualPalette && selectedTask) {
contextualPalette = React.createElement(barProps.ContextualPalette, {
selectedTask,
onClose,
onClosePalette: onClose,
});
} else {
contextualPalette = <div></div>;
Expand All @@ -118,7 +177,6 @@ const TaskGanttInner: React.FC<TaskGanttProps> = ({
}
}
};

return (
<div
className={styles.ganttTaskRoot}
Expand All @@ -143,7 +201,11 @@ const TaskGanttInner: React.FC<TaskGanttProps> = ({
ref={ganttSVGRef}
>
<Grid {...gridProps} />
<TaskGanttContent {...barProps} onClick={onClickTask} />
<TaskGanttContent
{...barProps}
onClick={onClickTask}
onArrowClick={onClickArrow}
/>
</svg>
</div>
{barProps.ContextualPalette && open && (
Expand All @@ -160,6 +222,21 @@ const TaskGanttInner: React.FC<TaskGanttProps> = ({
</Popper>
</ClickAwayListener>
)}
{barProps.TaskDependencyContextualPalette &&
isArrowContextualPaletteOpened && (
<ClickAwayListener onClickAway={onArrowClickAway}>
<Popper
key={`dependency-contextual-palette`}
open={isArrowContextualPaletteOpened}
anchorEl={arrowAnchorEl}
transition
disablePortal
placement="top"
>
<Paper>{arrowContextualPalette}</Paper>
</Popper>
</ClickAwayListener>
)}
</div>
</div>
);
Expand Down
27 changes: 14 additions & 13 deletions src/components/gantt/use-create-relation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
Task,
TaskMapByLevel,
TaskOrEmpty,
RelationMoveTarget,
DateExtremity,
} from "../../types/public-types";

type UseCreateRelationParams = {
Expand Down Expand Up @@ -44,7 +44,7 @@ export const useCreateRelation = ({
visibleTasks,
}: UseCreateRelationParams): [
GanttRelationEvent | null,
(target: RelationMoveTarget, task: Task) => void
(extremity: DateExtremity, task: Task) => void
] => {
const [ganttRelationEvent, setGanttRelationEvent] =
useState<GanttRelationEvent | null>(null);
Expand All @@ -53,16 +53,16 @@ export const useCreateRelation = ({
* Method is Start point of start draw relation
*/
const handleBarRelationStart = useCallback(
(target: RelationMoveTarget, task: Task) => {
(extremity: DateExtremity, task: Task) => {
const coordinates = getTaskCoordinates(task, mapTaskToCoordinates);
const startX =
(target === "startOfTask") !== rtl
(extremity === "startOfTask") !== rtl
? coordinates.x1 - 10
: coordinates.x2 + 10;
const startY = coordinates.y + taskHalfHeight;

setGanttRelationEvent({
target,
extremity,
task,
startX,
startY,
Expand All @@ -73,14 +73,14 @@ export const useCreateRelation = ({
[taskHalfHeight, mapTaskToCoordinates, rtl]
);

const startRelationTarget = ganttRelationEvent?.target;
const startRelationExtremity = ganttRelationEvent?.extremity;
const startRelationTask = ganttRelationEvent?.task;

/**
* Drag arrow
*/
useEffect(() => {
if (!onRelationChange || !startRelationTarget || !startRelationTask) {
if (!onRelationChange || !startRelationExtremity || !startRelationTask) {
return undefined;
}

Expand Down Expand Up @@ -148,7 +148,7 @@ export const useCreateRelation = ({

const svgP = point.matrixTransform(ctm.inverse());

const endTargetRelationCircle = getRelationCircleByCoordinates(
const endExtremityRelationCircle = getRelationCircleByCoordinates(
svgP,
visibleTasks,
taskHalfHeight,
Expand All @@ -158,8 +158,9 @@ export const useCreateRelation = ({
getMapTaskToCoordinatesOnLevel(startRelationTask, mapTaskToCoordinates)
);

if (endTargetRelationCircle) {
const [endRelationTask, endRelationTarget] = endTargetRelationCircle;
if (endExtremityRelationCircle) {
const [endRelationTask, endRelationExtremity] =
endExtremityRelationCircle;

const { comparisonLevel: startComparisonLevel = 1 } = startRelationTask;

Expand Down Expand Up @@ -195,8 +196,8 @@ export const useCreateRelation = ({
checkIsDescendant(endRelationTask, startRelationTask, tasksMap);

onRelationChange(
[startRelationTask, startRelationTarget, startIndex],
[endRelationTask, endRelationTarget, endIndex],
[startRelationTask, startRelationExtremity, startIndex],
[endRelationTask, endRelationExtremity, endIndex],
isOneDescendant
);
}
Expand Down Expand Up @@ -233,7 +234,7 @@ export const useCreateRelation = ({
}, [
ganttSVGRef,
rtl,
startRelationTarget,
startRelationExtremity,
startRelationTask,
setGanttRelationEvent,
mapTaskToCoordinates,
Expand Down
2 changes: 1 addition & 1 deletion src/components/grid/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Grid: React.FC<GridProps> = props => {
return rest === -1 || rest === -2;
}

return checkIsHoliday(date, "start");
return checkIsHoliday(date, "startOfTask");
};

const renderedHolidays = useMemo(() => {
Expand Down
Loading

0 comments on commit f1942bf

Please sign in to comment.