RTL + NEW ROWS
This commit is contained in:
parent
6c51146f91
commit
a85961cc20
@ -64,20 +64,17 @@ const App = () => {
|
|||||||
isChecked={isChecked}
|
isChecked={isChecked}
|
||||||
/>
|
/>
|
||||||
<h3>Gantt With Unlimited Height</h3>
|
<h3>Gantt With Unlimited Height</h3>
|
||||||
<div dir="rtl">
|
<Gantt
|
||||||
<Gantt
|
tasks={tasks}
|
||||||
tasks={tasks}
|
viewMode={view}
|
||||||
viewMode={view}
|
onDateChange={onTaskChange}
|
||||||
onDateChange={onTaskChange}
|
onDelete={onTaskDelete}
|
||||||
onDelete={onTaskDelete}
|
onProgressChange={onProgressChange}
|
||||||
onProgressChange={onProgressChange}
|
onDoubleClick={onDblClick}
|
||||||
onDoubleClick={onDblClick}
|
onSelect={onSelect}
|
||||||
onSelect={onSelect}
|
listCellWidth={isChecked ? "155px" : ""}
|
||||||
listCellWidth={isChecked ? "155px" : ""}
|
columnWidth={columnWidth}
|
||||||
columnWidth={columnWidth}
|
/>
|
||||||
rtl={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h3>Gantt With Limited Height</h3>
|
<h3>Gantt With Limited Height</h3>
|
||||||
<Gantt
|
<Gantt
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { ReactChild } from "react";
|
|||||||
import { ViewMode } from "../../types/public-types";
|
import { ViewMode } from "../../types/public-types";
|
||||||
import { TopPartOfCalendar } from "./top-part-of-calendar";
|
import { TopPartOfCalendar } from "./top-part-of-calendar";
|
||||||
import {
|
import {
|
||||||
|
getDaysInMonth,
|
||||||
getLocaleMonth,
|
getLocaleMonth,
|
||||||
getWeekNumberISO8601,
|
getWeekNumberISO8601,
|
||||||
} from "../../helpers/date-helper";
|
} from "../../helpers/date-helper";
|
||||||
@ -12,6 +13,7 @@ export type CalendarProps = {
|
|||||||
dateSetup: DateSetup;
|
dateSetup: DateSetup;
|
||||||
locale: string;
|
locale: string;
|
||||||
viewMode: ViewMode;
|
viewMode: ViewMode;
|
||||||
|
rtl: boolean;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
columnWidth: number;
|
columnWidth: number;
|
||||||
fontFamily: string;
|
fontFamily: string;
|
||||||
@ -22,6 +24,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
dateSetup,
|
dateSetup,
|
||||||
locale,
|
locale,
|
||||||
viewMode,
|
viewMode,
|
||||||
|
rtl,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
@ -30,7 +33,6 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
const getCalendarValuesForMonth = () => {
|
const getCalendarValuesForMonth = () => {
|
||||||
const topValues: ReactChild[] = [];
|
const topValues: ReactChild[] = [];
|
||||||
const bottomValues: ReactChild[] = [];
|
const bottomValues: ReactChild[] = [];
|
||||||
const topDefaultWidth = columnWidth * 6;
|
|
||||||
const topDefaultHeight = headerHeight * 0.5;
|
const topDefaultHeight = headerHeight * 0.5;
|
||||||
for (let i = 0; i < dateSetup.dates.length; i++) {
|
for (let i = 0; i < dateSetup.dates.length; i++) {
|
||||||
const date = dateSetup.dates[i];
|
const date = dateSetup.dates[i];
|
||||||
@ -50,6 +52,12 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()
|
date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()
|
||||||
) {
|
) {
|
||||||
const topValue = date.getFullYear().toString();
|
const topValue = date.getFullYear().toString();
|
||||||
|
let xText: number;
|
||||||
|
if (rtl) {
|
||||||
|
xText = (6 + i + date.getMonth() + 1) * columnWidth;
|
||||||
|
} else {
|
||||||
|
xText = (6 + i - date.getMonth()) * columnWidth;
|
||||||
|
}
|
||||||
topValues.push(
|
topValues.push(
|
||||||
<TopPartOfCalendar
|
<TopPartOfCalendar
|
||||||
key={topValue}
|
key={topValue}
|
||||||
@ -57,9 +65,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
x1Line={columnWidth * i}
|
x1Line={columnWidth * i}
|
||||||
y1Line={0}
|
y1Line={0}
|
||||||
y2Line={topDefaultHeight}
|
y2Line={topDefaultHeight}
|
||||||
xText={
|
xText={xText}
|
||||||
topDefaultWidth + columnWidth * i - date.getMonth() * columnWidth
|
|
||||||
}
|
|
||||||
yText={topDefaultHeight * 0.9}
|
yText={topDefaultHeight * 0.9}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -88,7 +94,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
<text
|
<text
|
||||||
key={date.getTime()}
|
key={date.getTime()}
|
||||||
y={headerHeight * 0.8}
|
y={headerHeight * 0.8}
|
||||||
x={columnWidth * i}
|
x={columnWidth * (i + +rtl)}
|
||||||
className={styles.calendarBottomText}
|
className={styles.calendarBottomText}
|
||||||
>
|
>
|
||||||
{bottomValue}
|
{bottomValue}
|
||||||
@ -149,7 +155,12 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
x1Line={columnWidth * (i + 1)}
|
x1Line={columnWidth * (i + 1)}
|
||||||
y1Line={0}
|
y1Line={0}
|
||||||
y2Line={topDefaultHeight}
|
y2Line={topDefaultHeight}
|
||||||
xText={columnWidth * (i + 1) - date.getDate() * columnWidth * 0.5}
|
xText={
|
||||||
|
columnWidth * (i + 1) -
|
||||||
|
getDaysInMonth(date.getMonth(), date.getFullYear()) *
|
||||||
|
columnWidth *
|
||||||
|
0.5
|
||||||
|
}
|
||||||
yText={topDefaultHeight * 0.9}
|
yText={topDefaultHeight * 0.9}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -174,7 +185,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
<text
|
<text
|
||||||
key={date.getTime()}
|
key={date.getTime()}
|
||||||
y={headerHeight * 0.8}
|
y={headerHeight * 0.8}
|
||||||
x={columnWidth * i}
|
x={columnWidth * (i + +rtl)}
|
||||||
className={styles.calendarBottomText}
|
className={styles.calendarBottomText}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
>
|
>
|
||||||
@ -196,8 +207,10 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [topValues, bottomValues];
|
return [topValues, bottomValues];
|
||||||
};
|
};
|
||||||
|
|
||||||
let topValues: ReactChild[] = [];
|
let topValues: ReactChild[] = [];
|
||||||
let bottomValues: ReactChild[] = [];
|
let bottomValues: ReactChild[] = [];
|
||||||
switch (dateSetup.viewMode) {
|
switch (dateSetup.viewMode) {
|
||||||
|
|||||||
@ -74,19 +74,22 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
const [selectedTask, setSelectedTask] = useState<BarTask>();
|
const [selectedTask, setSelectedTask] = useState<BarTask>();
|
||||||
const [failedTask, setFailedTask] = useState<BarTask | null>(null);
|
const [failedTask, setFailedTask] = useState<BarTask | null>(null);
|
||||||
|
|
||||||
const [scrollY, setScrollY] = useState(0);
|
|
||||||
const [scrollX, setScrollX] = useState(0);
|
|
||||||
const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false);
|
|
||||||
|
|
||||||
const svgWidth = dateSetup.dates.length * columnWidth;
|
const svgWidth = dateSetup.dates.length * columnWidth;
|
||||||
const ganttFullHeight = barTasks.length * rowHeight;
|
const ganttFullHeight = barTasks.length * rowHeight;
|
||||||
|
|
||||||
|
const [scrollY, setScrollY] = useState(0);
|
||||||
|
const [scrollX, setScrollX] = useState(-1);
|
||||||
|
const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false);
|
||||||
|
|
||||||
// task change events
|
// task change events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
||||||
let newDates = seedDates(startDate, endDate, viewMode);
|
let newDates = seedDates(startDate, endDate, viewMode);
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
newDates = newDates.reverse();
|
newDates = newDates.reverse();
|
||||||
|
if (scrollX === -1) {
|
||||||
|
setScrollX(newDates.length * columnWidth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setDateSetup({ dates: newDates, viewMode });
|
setDateSetup({ dates: newDates, viewMode });
|
||||||
setBarTasks(
|
setBarTasks(
|
||||||
@ -130,6 +133,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
milestoneBackgroundColor,
|
milestoneBackgroundColor,
|
||||||
milestoneBackgroundSelectedColor,
|
milestoneBackgroundSelectedColor,
|
||||||
rtl,
|
rtl,
|
||||||
|
scrollX,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -211,7 +215,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
}
|
}
|
||||||
setScrollX(newScrollX);
|
setScrollX(newScrollX);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
} else {
|
} else if (ganttHeight) {
|
||||||
let newScrollY = scrollY + event.deltaY;
|
let newScrollY = scrollY + event.deltaY;
|
||||||
if (newScrollY < 0) {
|
if (newScrollY < 0) {
|
||||||
newScrollY = 0;
|
newScrollY = 0;
|
||||||
@ -238,7 +242,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
wrapperRef.current.removeEventListener("wheel", handleWheel);
|
wrapperRef.current.removeEventListener("wheel", handleWheel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [wrapperRef.current, scrollY, scrollX, ganttHeight, svgWidth]);
|
}, [wrapperRef.current, scrollY, scrollX, ganttHeight, svgWidth, rtl]);
|
||||||
|
|
||||||
const handleScrollY = (event: SyntheticEvent<HTMLDivElement>) => {
|
const handleScrollY = (event: SyntheticEvent<HTMLDivElement>) => {
|
||||||
if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) {
|
if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) {
|
||||||
@ -326,6 +330,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
rowHeight,
|
rowHeight,
|
||||||
dates: dateSetup.dates,
|
dates: dateSetup.dates,
|
||||||
todayColor,
|
todayColor,
|
||||||
|
rtl,
|
||||||
};
|
};
|
||||||
const calendarProps: CalendarProps = {
|
const calendarProps: CalendarProps = {
|
||||||
dateSetup,
|
dateSetup,
|
||||||
@ -335,6 +340,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
columnWidth,
|
columnWidth,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
|
rtl,
|
||||||
};
|
};
|
||||||
const barProps: TaskGanttContentProps = {
|
const barProps: TaskGanttContentProps = {
|
||||||
tasks: barTasks,
|
tasks: barTasks,
|
||||||
@ -408,6 +414,8 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
taskListWidth={taskListWidth}
|
taskListWidth={taskListWidth}
|
||||||
TooltipContent={TooltipContent}
|
TooltipContent={TooltipContent}
|
||||||
|
rtl={rtl}
|
||||||
|
svgWidth={svgWidth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<VerticalScroll
|
<VerticalScroll
|
||||||
@ -416,6 +424,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
|
|||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
scroll={scrollY}
|
scroll={scrollY}
|
||||||
onScroll={handleScrollY}
|
onScroll={handleScrollY}
|
||||||
|
rtl={rtl}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HorizontalScroll
|
<HorizontalScroll
|
||||||
|
|||||||
@ -260,6 +260,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
taskHeight={taskHeight}
|
taskHeight={taskHeight}
|
||||||
arrowIndent={arrowIndent}
|
arrowIndent={arrowIndent}
|
||||||
|
rtl={rtl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -278,6 +279,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
onEventStart={handleBarEventStart}
|
onEventStart={handleBarEventStart}
|
||||||
key={task.id}
|
key={task.id}
|
||||||
isSelected={!!selectedTask && task.id === selectedTask.id}
|
isSelected={!!selectedTask && task.id === selectedTask.id}
|
||||||
|
rtl={rtl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.ganttVerticalContainer}
|
className={styles.ganttVerticalContainer}
|
||||||
ref={verticalGanttContainerRef}
|
ref={verticalGanttContainerRef}
|
||||||
|
dir="ltr"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export type GridBodyProps = {
|
|||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
columnWidth: number;
|
columnWidth: number;
|
||||||
todayColor: string;
|
todayColor: string;
|
||||||
|
rtl: boolean;
|
||||||
};
|
};
|
||||||
export const GridBody: React.FC<GridBodyProps> = ({
|
export const GridBody: React.FC<GridBodyProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
@ -18,6 +19,7 @@ export const GridBody: React.FC<GridBodyProps> = ({
|
|||||||
svgWidth,
|
svgWidth,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
todayColor,
|
todayColor,
|
||||||
|
rtl,
|
||||||
}) => {
|
}) => {
|
||||||
let y = 0;
|
let y = 0;
|
||||||
const gridRows: ReactChild[] = [];
|
const gridRows: ReactChild[] = [];
|
||||||
@ -95,6 +97,23 @@ export const GridBody: React.FC<GridBodyProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// rtl for today
|
||||||
|
if (
|
||||||
|
rtl &&
|
||||||
|
i + 1 !== dates.length &&
|
||||||
|
date.getTime() >= now.getTime() &&
|
||||||
|
dates[i + 1].getTime() < now.getTime()
|
||||||
|
) {
|
||||||
|
today = (
|
||||||
|
<rect
|
||||||
|
x={tickX + columnWidth}
|
||||||
|
y={0}
|
||||||
|
width={columnWidth}
|
||||||
|
height={y}
|
||||||
|
fill={todayColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
tickX += columnWidth;
|
tickX += columnWidth;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,6 +7,7 @@ type ArrowProps = {
|
|||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
taskHeight: number;
|
taskHeight: number;
|
||||||
arrowIndent: number;
|
arrowIndent: number;
|
||||||
|
rtl: boolean;
|
||||||
};
|
};
|
||||||
export const Arrow: React.FC<ArrowProps> = ({
|
export const Arrow: React.FC<ArrowProps> = ({
|
||||||
taskFrom,
|
taskFrom,
|
||||||
@ -14,19 +15,28 @@ export const Arrow: React.FC<ArrowProps> = ({
|
|||||||
rowHeight,
|
rowHeight,
|
||||||
taskHeight,
|
taskHeight,
|
||||||
arrowIndent,
|
arrowIndent,
|
||||||
|
rtl,
|
||||||
}) => {
|
}) => {
|
||||||
const indexCompare = taskFrom.index > taskTo.index ? -1 : 1;
|
let path: string;
|
||||||
const taskToEndPosition = taskTo.y + taskHeight / 2;
|
let trianglePoints: string;
|
||||||
|
if (rtl) {
|
||||||
|
[path, trianglePoints] = drownPathAndTriangleRTL(
|
||||||
|
taskFrom,
|
||||||
|
taskTo,
|
||||||
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
|
arrowIndent
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
[path, trianglePoints] = drownPathAndTriangle(
|
||||||
|
taskFrom,
|
||||||
|
taskTo,
|
||||||
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
|
arrowIndent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const path = `M ${taskFrom.x2} ${taskFrom.y + taskHeight / 2}
|
|
||||||
h ${arrowIndent}
|
|
||||||
v ${(indexCompare * rowHeight) / 2}
|
|
||||||
H ${taskTo.x1 - arrowIndent}
|
|
||||||
V ${taskToEndPosition}
|
|
||||||
h ${arrowIndent}`;
|
|
||||||
const trianglePoints = `${taskTo.x1},${taskToEndPosition}
|
|
||||||
${taskTo.x1 - 5},${taskToEndPosition - 5}
|
|
||||||
${taskTo.x1 - 5},${taskToEndPosition + 5}`;
|
|
||||||
return (
|
return (
|
||||||
<g className="arrow">
|
<g className="arrow">
|
||||||
<path strokeWidth="1.5" d={path} fill="none" />
|
<path strokeWidth="1.5" d={path} fill="none" />
|
||||||
@ -34,3 +44,63 @@ export const Arrow: React.FC<ArrowProps> = ({
|
|||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const drownPathAndTriangle = (
|
||||||
|
taskFrom: BarTask,
|
||||||
|
taskTo: BarTask,
|
||||||
|
rowHeight: number,
|
||||||
|
taskHeight: number,
|
||||||
|
arrowIndent: number
|
||||||
|
) => {
|
||||||
|
const indexCompare = taskFrom.index > taskTo.index ? -1 : 1;
|
||||||
|
const taskToEndPosition = taskTo.y + taskHeight / 2;
|
||||||
|
const taskFromEndPosition = taskFrom.x2 + arrowIndent * 2;
|
||||||
|
const taskFromHorizontalOffsetValue =
|
||||||
|
taskFromEndPosition < taskTo.x1 ? "" : `H ${taskTo.x1 - arrowIndent}`;
|
||||||
|
const taskToHorizontalOffsetValue =
|
||||||
|
taskFromEndPosition > taskTo.x1
|
||||||
|
? arrowIndent
|
||||||
|
: taskTo.x1 - taskFrom.x2 - arrowIndent;
|
||||||
|
|
||||||
|
const path = `M ${taskFrom.x2} ${taskFrom.y + taskHeight / 2}
|
||||||
|
h ${arrowIndent}
|
||||||
|
v ${(indexCompare * rowHeight) / 2}
|
||||||
|
${taskFromHorizontalOffsetValue}
|
||||||
|
V ${taskToEndPosition}
|
||||||
|
h ${taskToHorizontalOffsetValue}`;
|
||||||
|
|
||||||
|
const trianglePoints = `${taskTo.x1},${taskToEndPosition}
|
||||||
|
${taskTo.x1 - 5},${taskToEndPosition - 5}
|
||||||
|
${taskTo.x1 - 5},${taskToEndPosition + 5}`;
|
||||||
|
return [path, trianglePoints];
|
||||||
|
};
|
||||||
|
|
||||||
|
const drownPathAndTriangleRTL = (
|
||||||
|
taskFrom: BarTask,
|
||||||
|
taskTo: BarTask,
|
||||||
|
rowHeight: number,
|
||||||
|
taskHeight: number,
|
||||||
|
arrowIndent: number
|
||||||
|
) => {
|
||||||
|
const indexCompare = taskFrom.index > taskTo.index ? -1 : 1;
|
||||||
|
const taskToEndPosition = taskTo.y + taskHeight / 2;
|
||||||
|
const taskFromEndPosition = taskFrom.x1 - arrowIndent * 2;
|
||||||
|
const taskFromHorizontalOffsetValue =
|
||||||
|
taskFromEndPosition > taskTo.x2 ? "" : `H ${taskTo.x2 + arrowIndent}`;
|
||||||
|
const taskToHorizontalOffsetValue =
|
||||||
|
taskFromEndPosition < taskTo.x2
|
||||||
|
? -arrowIndent
|
||||||
|
: taskTo.x2 - taskFrom.x1 + arrowIndent;
|
||||||
|
|
||||||
|
const path = `M ${taskFrom.x1} ${taskFrom.y + taskHeight / 2}
|
||||||
|
h ${-arrowIndent}
|
||||||
|
v ${(indexCompare * rowHeight) / 2}
|
||||||
|
${taskFromHorizontalOffsetValue}
|
||||||
|
V ${taskToEndPosition}
|
||||||
|
h ${taskToHorizontalOffsetValue}`;
|
||||||
|
|
||||||
|
const trianglePoints = `${taskTo.x2},${taskToEndPosition}
|
||||||
|
${taskTo.x2 + 5},${taskToEndPosition + 5}
|
||||||
|
${taskTo.x2 + 5},${taskToEndPosition - 5}`;
|
||||||
|
return [path, trianglePoints];
|
||||||
|
};
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export const HorizontalScroll: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
dir="ltr"
|
||||||
style={{
|
style={{
|
||||||
margin: rtl
|
margin: rtl
|
||||||
? `0px ${taskListWidth}px 0px 0px`
|
? `0px ${taskListWidth}px 0px 0px`
|
||||||
|
|||||||
@ -6,8 +6,10 @@ import styles from "./tooltip.module.css";
|
|||||||
export type TooltipProps = {
|
export type TooltipProps = {
|
||||||
task: BarTask;
|
task: BarTask;
|
||||||
arrowIndent: number;
|
arrowIndent: number;
|
||||||
|
rtl: boolean;
|
||||||
svgContainerHeight: number;
|
svgContainerHeight: number;
|
||||||
svgContainerWidth: number;
|
svgContainerWidth: number;
|
||||||
|
svgWidth: number;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
taskListWidth: number;
|
taskListWidth: number;
|
||||||
scrollX: number;
|
scrollX: number;
|
||||||
@ -24,6 +26,7 @@ export type TooltipProps = {
|
|||||||
export const Tooltip: React.FC<TooltipProps> = ({
|
export const Tooltip: React.FC<TooltipProps> = ({
|
||||||
task,
|
task,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
|
rtl,
|
||||||
svgContainerHeight,
|
svgContainerHeight,
|
||||||
svgContainerWidth,
|
svgContainerWidth,
|
||||||
scrollX,
|
scrollX,
|
||||||
@ -40,30 +43,41 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
|||||||
const [relatedX, setRelatedX] = useState(0);
|
const [relatedX, setRelatedX] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tooltipRef.current) {
|
if (tooltipRef.current) {
|
||||||
let newRelatedX =
|
|
||||||
task.x2 + arrowIndent + arrowIndent * 0.5 + taskListWidth - scrollX;
|
|
||||||
let newRelatedY = task.index * rowHeight - scrollY + headerHeight;
|
|
||||||
|
|
||||||
const tooltipHeight = tooltipRef.current.offsetHeight * 1.1;
|
const tooltipHeight = tooltipRef.current.offsetHeight * 1.1;
|
||||||
const tooltipWidth = tooltipRef.current.offsetWidth * 1.1;
|
const tooltipWidth = tooltipRef.current.offsetWidth * 1.1;
|
||||||
|
|
||||||
const tooltipLowerPoint = tooltipHeight + newRelatedY - scrollY;
|
let newRelatedY = task.index * rowHeight - scrollY + headerHeight;
|
||||||
const tooltipLeftmostPoint = tooltipWidth + newRelatedX;
|
let newRelatedX: number;
|
||||||
const fullChartWidth = taskListWidth + svgContainerWidth;
|
if (rtl) {
|
||||||
|
newRelatedX = task.x1 - arrowIndent * 1.5 - tooltipWidth - scrollX;
|
||||||
if (tooltipLeftmostPoint > fullChartWidth) {
|
if (newRelatedX < 0) {
|
||||||
newRelatedX =
|
newRelatedX = task.x2 + arrowIndent * 1.5 - scrollX;
|
||||||
task.x1 +
|
}
|
||||||
taskListWidth -
|
const tooltipLeftmostPoint = tooltipWidth + newRelatedX;
|
||||||
arrowIndent -
|
if (tooltipLeftmostPoint > svgContainerWidth) {
|
||||||
arrowIndent * 0.5 -
|
newRelatedX = svgContainerWidth - tooltipWidth;
|
||||||
scrollX -
|
newRelatedY += rowHeight;
|
||||||
tooltipWidth;
|
}
|
||||||
|
} else {
|
||||||
|
newRelatedX = task.x2 + arrowIndent * 1.5 + taskListWidth - scrollX;
|
||||||
|
const tooltipLeftmostPoint = tooltipWidth + newRelatedX;
|
||||||
|
const fullChartWidth = taskListWidth + svgContainerWidth;
|
||||||
|
if (tooltipLeftmostPoint > fullChartWidth) {
|
||||||
|
newRelatedX =
|
||||||
|
task.x1 +
|
||||||
|
taskListWidth -
|
||||||
|
arrowIndent * 1.5 -
|
||||||
|
scrollX -
|
||||||
|
tooltipWidth;
|
||||||
|
}
|
||||||
|
if (newRelatedX < taskListWidth) {
|
||||||
|
newRelatedX = svgContainerWidth + taskListWidth - tooltipWidth;
|
||||||
|
newRelatedY += rowHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (newRelatedX < taskListWidth) {
|
|
||||||
newRelatedX = svgContainerWidth + taskListWidth - tooltipWidth;
|
const tooltipLowerPoint = tooltipHeight + newRelatedY - scrollY;
|
||||||
newRelatedY += rowHeight;
|
if (tooltipLowerPoint > svgContainerHeight - scrollY) {
|
||||||
} else if (tooltipLowerPoint > svgContainerHeight - scrollY) {
|
|
||||||
newRelatedY = svgContainerHeight - tooltipHeight;
|
newRelatedY = svgContainerHeight - tooltipHeight;
|
||||||
}
|
}
|
||||||
setRelatedY(newRelatedY);
|
setRelatedY(newRelatedY);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
.scroll {
|
.scroll {
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
margin-left: -17px;
|
|
||||||
width: 17px;
|
width: 17px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,16 @@ export const VerticalScroll: React.FC<{
|
|||||||
ganttHeight: number;
|
ganttHeight: number;
|
||||||
ganttFullHeight: number;
|
ganttFullHeight: number;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
|
rtl: boolean;
|
||||||
onScroll: (event: SyntheticEvent<HTMLDivElement>) => void;
|
onScroll: (event: SyntheticEvent<HTMLDivElement>) => void;
|
||||||
}> = ({ scroll, ganttHeight, ganttFullHeight, headerHeight, onScroll }) => {
|
}> = ({
|
||||||
|
scroll,
|
||||||
|
ganttHeight,
|
||||||
|
ganttFullHeight,
|
||||||
|
headerHeight,
|
||||||
|
rtl,
|
||||||
|
onScroll,
|
||||||
|
}) => {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -18,7 +26,11 @@ export const VerticalScroll: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ height: ganttHeight, marginTop: headerHeight }}
|
style={{
|
||||||
|
height: ganttHeight,
|
||||||
|
marginTop: headerHeight,
|
||||||
|
marginLeft: rtl ? "" : "-17px",
|
||||||
|
}}
|
||||||
className={styles.scroll}
|
className={styles.scroll}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
|
|||||||
@ -10,11 +10,12 @@ export const Bar: React.FC<TaskItemProps> = ({
|
|||||||
task,
|
task,
|
||||||
isProgressChangeable,
|
isProgressChangeable,
|
||||||
isDateChangeable,
|
isDateChangeable,
|
||||||
|
rtl,
|
||||||
onEventStart,
|
onEventStart,
|
||||||
isSelected,
|
isSelected,
|
||||||
}) => {
|
}) => {
|
||||||
const progressPoint = getProgressPoint(
|
const progressPoint = getProgressPoint(
|
||||||
task.progressWidth + task.x1,
|
+!rtl * task.progressWidth + task.progressX,
|
||||||
task.y,
|
task.y,
|
||||||
task.height
|
task.height
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export type TaskItemProps = {
|
|||||||
isDateChangeable: boolean;
|
isDateChangeable: boolean;
|
||||||
isDelete: boolean;
|
isDelete: boolean;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
rtl: boolean;
|
||||||
onEventStart: (
|
onEventStart: (
|
||||||
action: GanttContentMoveAction,
|
action: GanttContentMoveAction,
|
||||||
selectedTask: BarTask,
|
selectedTask: BarTask,
|
||||||
@ -29,6 +30,7 @@ export const TaskItem: React.FC<TaskItemProps> = props => {
|
|||||||
isDelete,
|
isDelete,
|
||||||
taskHeight,
|
taskHeight,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
rtl,
|
||||||
onEventStart,
|
onEventStart,
|
||||||
} = {
|
} = {
|
||||||
...props,
|
...props,
|
||||||
@ -63,9 +65,19 @@ export const TaskItem: React.FC<TaskItemProps> = props => {
|
|||||||
const getX = () => {
|
const getX = () => {
|
||||||
const width = task.x2 - task.x1;
|
const width = task.x2 - task.x1;
|
||||||
const hasChild = task.barChildren.length > 0;
|
const hasChild = task.barChildren.length > 0;
|
||||||
return isTextInside
|
if (isTextInside) {
|
||||||
? task.x1 + width * 0.5
|
return task.x1 + width * 0.5;
|
||||||
: task.x1 + width + arrowIndent * +hasChild + arrowIndent * 0.2;
|
}
|
||||||
|
if (rtl && textRef.current) {
|
||||||
|
return (
|
||||||
|
task.x1 -
|
||||||
|
textRef.current.getBBox().width -
|
||||||
|
arrowIndent * +hasChild -
|
||||||
|
arrowIndent * 0.2
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return task.x1 + width + arrowIndent * +hasChild + arrowIndent * 0.2;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const TaskListTableDefault: React.FC<{
|
|||||||
selectedTaskId: string;
|
selectedTaskId: string;
|
||||||
setSelectedTask: (taskId: string) => void;
|
setSelectedTask: (taskId: string) => void;
|
||||||
}> = ({ rowHeight, rowWidth, tasks, fontFamily, fontSize, locale }) => {
|
}> = ({ rowHeight, rowWidth, tasks, fontFamily, fontSize, locale }) => {
|
||||||
const dateTimeOptions = {
|
const dateTimeOptions: Intl.DateTimeFormatOptions = {
|
||||||
weekday: "short",
|
weekday: "short",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
|
|||||||
@ -165,8 +165,8 @@ const convertToBar = (
|
|||||||
let x1: number;
|
let x1: number;
|
||||||
let x2: number;
|
let x2: number;
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
x2 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
x2 = taskXCoordinateRTL(task.start, dates, dateDelta, columnWidth);
|
||||||
x1 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
x1 = taskXCoordinateRTL(task.end, dates, dateDelta, columnWidth);
|
||||||
} else {
|
} else {
|
||||||
x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
||||||
x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
||||||
@ -177,14 +177,12 @@ const convertToBar = (
|
|||||||
x2 = x1 + handleWidth * 2;
|
x2 = x1 + handleWidth * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const progressWidth = progressWithByParams(x1, x2, task.progress);
|
const [progressWidth, progressX] = progressWithByParams(
|
||||||
let progressX: number;
|
x1,
|
||||||
if (rtl) {
|
x2,
|
||||||
progressX = x2 - progressWidth;
|
task.progress,
|
||||||
} else {
|
rtl
|
||||||
progressX = x1;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
@ -281,7 +279,16 @@ const taskXCoordinate = (
|
|||||||
);
|
);
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
const taskXCoordinateRTL = (
|
||||||
|
xDate: Date,
|
||||||
|
dates: Date[],
|
||||||
|
dateDelta: number,
|
||||||
|
columnWidth: number
|
||||||
|
) => {
|
||||||
|
let x = taskXCoordinate(xDate, dates, dateDelta, columnWidth);
|
||||||
|
x += columnWidth;
|
||||||
|
return x;
|
||||||
|
};
|
||||||
const taskYCoordinate = (
|
const taskYCoordinate = (
|
||||||
index: number,
|
index: number,
|
||||||
rowHeight: number,
|
rowHeight: number,
|
||||||
@ -294,9 +301,17 @@ const taskYCoordinate = (
|
|||||||
export const progressWithByParams = (
|
export const progressWithByParams = (
|
||||||
taskX1: number,
|
taskX1: number,
|
||||||
taskX2: number,
|
taskX2: number,
|
||||||
progress: number
|
progress: number,
|
||||||
|
rtl: boolean
|
||||||
) => {
|
) => {
|
||||||
return (taskX2 - taskX1) * progress * 0.01;
|
const progressWidth = (taskX2 - taskX1) * progress * 0.01;
|
||||||
|
let progressX: number;
|
||||||
|
if (rtl) {
|
||||||
|
progressX = taskX2 - progressWidth;
|
||||||
|
} else {
|
||||||
|
progressX = taskX1;
|
||||||
|
}
|
||||||
|
return [progressWidth, progressX];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const progressByProgressWidth = (
|
export const progressByProgressWidth = (
|
||||||
@ -319,6 +334,15 @@ const progressByX = (x: number, task: BarTask) => {
|
|||||||
return progressPercent;
|
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 = (
|
export const getProgressPoint = (
|
||||||
progressX: number,
|
progressX: number,
|
||||||
@ -431,19 +455,21 @@ const handleTaskBySVGMouseEventForBar = (
|
|||||||
let isChanged = false;
|
let isChanged = false;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "progress":
|
case "progress":
|
||||||
changedTask.progress = progressByX(svgX, selectedTask);
|
if (rtl) {
|
||||||
|
changedTask.progress = progressByXRTL(svgX, selectedTask);
|
||||||
|
} else {
|
||||||
|
changedTask.progress = progressByX(svgX, selectedTask);
|
||||||
|
}
|
||||||
isChanged = changedTask.progress !== selectedTask.progress;
|
isChanged = changedTask.progress !== selectedTask.progress;
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
changedTask.progressWidth = progressWithByParams(
|
const [progressWidth, progressX] = progressWithByParams(
|
||||||
changedTask.x1,
|
changedTask.x1,
|
||||||
changedTask.x2,
|
changedTask.x2,
|
||||||
changedTask.progress
|
changedTask.progress,
|
||||||
|
rtl
|
||||||
);
|
);
|
||||||
if (rtl) {
|
changedTask.progressWidth = progressWidth;
|
||||||
changedTask.progressX = changedTask.x2 - changedTask.progressWidth;
|
changedTask.progressX = progressX;
|
||||||
} else {
|
|
||||||
changedTask.progressX = changedTask.x1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "start": {
|
case "start": {
|
||||||
@ -451,13 +477,31 @@ const handleTaskBySVGMouseEventForBar = (
|
|||||||
changedTask.x1 = newX1;
|
changedTask.x1 = newX1;
|
||||||
isChanged = changedTask.x1 !== selectedTask.x1;
|
isChanged = changedTask.x1 !== selectedTask.x1;
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
changedTask.start = dateByX(
|
if (rtl) {
|
||||||
newX1,
|
changedTask.end = dateByX(
|
||||||
selectedTask.x1,
|
newX1,
|
||||||
selectedTask.start,
|
selectedTask.x1,
|
||||||
xStep,
|
selectedTask.end,
|
||||||
timeStep
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@ -466,13 +510,31 @@ const handleTaskBySVGMouseEventForBar = (
|
|||||||
changedTask.x2 = newX2;
|
changedTask.x2 = newX2;
|
||||||
isChanged = changedTask.x2 !== selectedTask.x2;
|
isChanged = changedTask.x2 !== selectedTask.x2;
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
changedTask.end = dateByX(
|
if (rtl) {
|
||||||
newX2,
|
changedTask.start = dateByX(
|
||||||
selectedTask.x2,
|
newX2,
|
||||||
selectedTask.end,
|
selectedTask.x2,
|
||||||
xStep,
|
selectedTask.start,
|
||||||
timeStep
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@ -500,6 +562,14 @@ const handleTaskBySVGMouseEventForBar = (
|
|||||||
);
|
);
|
||||||
changedTask.x1 = newMoveX1;
|
changedTask.x1 = newMoveX1;
|
||||||
changedTask.x2 = newMoveX2;
|
changedTask.x2 = newMoveX2;
|
||||||
|
const [progressWidth, progressX] = progressWithByParams(
|
||||||
|
changedTask.x1,
|
||||||
|
changedTask.x2,
|
||||||
|
changedTask.progress,
|
||||||
|
rtl
|
||||||
|
);
|
||||||
|
changedTask.progressWidth = progressWidth;
|
||||||
|
changedTask.progressX = progressX;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,11 +83,17 @@ export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
|
|||||||
newStartDate = addToDate(newStartDate, -1, "day");
|
newStartDate = addToDate(newStartDate, -1, "day");
|
||||||
newEndDate = addToDate(newEndDate, 19, "day");
|
newEndDate = addToDate(newEndDate, 19, "day");
|
||||||
break;
|
break;
|
||||||
default:
|
case ViewMode.QuarterDay:
|
||||||
newStartDate = startOfDate(newStartDate, "day");
|
newStartDate = startOfDate(newStartDate, "day");
|
||||||
newEndDate = startOfDate(newEndDate, "day");
|
newEndDate = startOfDate(newEndDate, "day");
|
||||||
newStartDate = addToDate(newStartDate, -1, "day");
|
newStartDate = addToDate(newStartDate, -1, "day");
|
||||||
newEndDate = addToDate(newEndDate, 5, "day");
|
newEndDate = addToDate(newEndDate, 66, "hour"); // 24(1 day)*3 - 6
|
||||||
|
break;
|
||||||
|
case ViewMode.HalfDay:
|
||||||
|
newStartDate = startOfDate(newStartDate, "day");
|
||||||
|
newEndDate = startOfDate(newEndDate, "day");
|
||||||
|
newStartDate = addToDate(newStartDate, -1, "day");
|
||||||
|
newEndDate = addToDate(newEndDate, 108, "hour"); // 24(1 day)*5 - 12
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return [newStartDate, newEndDate];
|
return [newStartDate, newEndDate];
|
||||||
@ -163,3 +169,7 @@ export const getWeekNumberISO8601 = (date: Date) => {
|
|||||||
return weekNumber;
|
return weekNumber;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDaysInMonth = (month: number, year: number) => {
|
||||||
|
return new Date(year, month + 1, 0).getDate();
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user