Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions web/ce/store/timeline/base-timeline.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getDateFromPositionOnGantt, getItemPositionWidth } from "@/components/g
// helpers
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
// store
import { CoreRootStore } from "@/store/root.store";
import { RootStore } from "@/plane-web/store/root.store";

// types
type BlockData = {
Expand Down Expand Up @@ -38,6 +38,7 @@ export interface IBaseTimelineStore {
updateCurrentViewData: (data: ChartDataType | undefined) => void;
updateActiveBlockId: (blockId: string | null) => void;
updateRenderView: (data: any) => void;
updateAllBlocksOnChartChangeWhileDragging: (addedWidth: number) => void;
getUpdatedPositionAfterDrag: (id: string, ignoreDependencies?: boolean) => IBlockUpdateDependencyData[];
updateBlockPosition: (id: string, deltaLeft: number, deltaWidth: number, ignoreDependencies?: boolean) => void;
getNumberOfDaysFromPosition: (position: number | undefined) => number | undefined;
Expand All @@ -57,9 +58,9 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
activeBlockId: string | null = null;
renderView: any = [];

rootStore: CoreRootStore;
rootStore: RootStore;

constructor(_rootStore: CoreRootStore) {
constructor(_rootStore: RootStore) {
makeObservable(this, {
// observables
blocksMap: observable,
Expand Down Expand Up @@ -232,6 +233,24 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
return getDateFromPositionOnGantt(position, this.currentViewData, offsetDays);
});

/**
* Adds width on Chart position change while the blocks are being dragged
* @param addedWidth
*/
updateAllBlocksOnChartChangeWhileDragging = action((addedWidth: number) => {
if (!this.blockIds || !this.isDragging) return;

runInAction(() => {
this.blockIds?.forEach((blockId) => {
const currBlock = this.blocksMap[blockId];

if (!currBlock || !currBlock.position) return;

currBlock.position.marginLeft += addedWidth;
});
});
});
Comment on lines +236 to +252
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding validation and optimizing performance.

The new method implementation has several areas for improvement:

  1. Input validation: Consider validating the addedWidth parameter.
  2. Error handling: Add error handling for invalid block positions.
  3. Performance: Consider updating only visible blocks instead of all blocks.

Here's a suggested implementation with these improvements:

  updateAllBlocksOnChartChangeWhileDragging = action((addedWidth: number) => {
+   if (typeof addedWidth !== 'number' || isNaN(addedWidth)) {
+     console.warn('Invalid width adjustment provided');
+     return;
+   }
    if (!this.blockIds || !this.isDragging) return;

    runInAction(() => {
-     this.blockIds?.forEach((blockId) => {
+     // Optional: Filter visible blocks first
+     const visibleBlocks = this.blockIds?.filter((blockId) => {
+       const block = this.blocksMap[blockId];
+       return block?.position?.marginLeft !== undefined;
+     });
+     
+     visibleBlocks?.forEach((blockId) => {
        const currBlock = this.blocksMap[blockId];

        if (!currBlock || !currBlock.position) return;

+       try {
          currBlock.position.marginLeft += addedWidth;
+       } catch (error) {
+         console.error(`Failed to update position for block ${blockId}:`, error);
+       }
      });
    });
  });

Committable suggestion was skipped due to low confidence.


/**
* returns updates dates of blocks post drag.
* @param id
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/gantt-chart/blocks/block-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const BlockRow: React.FC<Props> = observer((props) => {
(entries) => {
entries.forEach((entry) => {
setIsHidden(!entry.isIntersecting);
setIsBlockHiddenOnLeft(entry.boundingClientRect.left < 0);
setIsBlockHiddenOnLeft(entry.boundingClientRect.right < (entry.rootBounds?.left ?? 0));
});
},
{
Expand Down
6 changes: 3 additions & 3 deletions web/core/components/gantt-chart/chart/main-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
const onScroll = (e: React.UIEvent<HTMLDivElement, UIEvent>) => {
const { clientWidth, scrollLeft, scrollWidth } = e.currentTarget;

const approxRangeLeft = scrollLeft >= clientWidth + 1000 ? 1000 : scrollLeft - clientWidth;
const approxRangeLeft = scrollLeft;
const approxRangeRight = scrollWidth - (scrollLeft + clientWidth);

if (approxRangeRight < 1000) updateCurrentViewRenderPayload("right", currentView);
if (approxRangeLeft < 1000) updateCurrentViewRenderPayload("left", currentView);
if (approxRangeRight < clientWidth) updateCurrentViewRenderPayload("right", currentView);
if (approxRangeLeft < clientWidth) updateCurrentViewRenderPayload("left", currentView);
Comment on lines +101 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve scroll handling readability and maintainability.

The scroll handling logic improvements are good - using clientWidth as a dynamic threshold is more responsive and adaptable than a fixed value. However, we can make it even better:

Consider these improvements:

-    const approxRangeLeft = scrollLeft;
-    const approxRangeRight = scrollWidth - (scrollLeft + clientWidth);
+    const distanceFromStart = scrollLeft;
+    const distanceFromEnd = scrollWidth - (scrollLeft + clientWidth);
+    const LOAD_THRESHOLD = clientWidth; // pixels before edge to trigger load
 
-    if (approxRangeRight < clientWidth) updateCurrentViewRenderPayload("right", currentView);
-    if (approxRangeLeft < clientWidth) updateCurrentViewRenderPayload("left", currentView);
+    if (distanceFromEnd < LOAD_THRESHOLD) updateCurrentViewRenderPayload("right", currentView);
+    if (distanceFromStart < LOAD_THRESHOLD) updateCurrentViewRenderPayload("left", currentView);

This refactor:

  1. Uses more descriptive variable names that better explain their purpose
  2. Extracts the threshold to a named constant for better maintainability
  3. Maintains the improved dynamic threshold behavior
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const approxRangeLeft = scrollLeft;
const approxRangeRight = scrollWidth - (scrollLeft + clientWidth);
if (approxRangeRight < 1000) updateCurrentViewRenderPayload("right", currentView);
if (approxRangeLeft < 1000) updateCurrentViewRenderPayload("left", currentView);
if (approxRangeRight < clientWidth) updateCurrentViewRenderPayload("right", currentView);
if (approxRangeLeft < clientWidth) updateCurrentViewRenderPayload("left", currentView);
const distanceFromStart = scrollLeft;
const distanceFromEnd = scrollWidth - (scrollLeft + clientWidth);
const LOAD_THRESHOLD = clientWidth; // pixels before edge to trigger load
if (distanceFromEnd < LOAD_THRESHOLD) updateCurrentViewRenderPayload("right", currentView);
if (distanceFromStart < LOAD_THRESHOLD) updateCurrentViewRenderPayload("left", currentView);

};

const CHART_VIEW_COMPONENTS: {
Expand Down
28 changes: 25 additions & 3 deletions web/core/components/gantt-chart/chart/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
import { SIDEBAR_WIDTH } from "../constants";
import { currentViewDataWithView } from "../data";
import { ChartDataType, IBlockUpdateData, IBlockUpdateDependencyData, TGanttViews } from "../types";
import { getNumberOfDaysBetweenTwoDates, IMonthBlock, IMonthView, IWeekBlock, timelineViewHelpers } from "../views";
import {
getNumberOfDaysBetweenTwoDates,
IMonthBlock,
IMonthView,
IWeekBlock,
monthView,
quarterView,
weekView,
} from "../views";

type ChartViewRootProps = {
border: boolean;
Expand All @@ -35,6 +43,12 @@ type ChartViewRootProps = {
showToday: boolean;
};

const timelineViewHelpers = {
week: weekView,
month: monthView,
quarter: quarterView,
};

export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
const {
border,
Expand Down Expand Up @@ -62,8 +76,15 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
const [itemsContainerWidth, setItemsContainerWidth] = useState(0);
const [fullScreenMode, setFullScreenMode] = useState(false);
// hooks
const { currentView, currentViewData, renderView, updateCurrentView, updateCurrentViewData, updateRenderView } =
useTimeLineChartStore();
const {
currentView,
currentViewData,
renderView,
updateCurrentView,
updateCurrentViewData,
updateRenderView,
updateAllBlocksOnChartChangeWhileDragging,
} = useTimeLineChartStore();

const updateCurrentViewRenderPayload = (side: null | "left" | "right", view: TGanttViews) => {
const selectedCurrentView: TGanttViews = view;
Expand All @@ -89,6 +110,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = observer((props) => {
updateCurrentView(selectedCurrentView);
updateRenderView(mergeRenderPayloads(currentRender.payload, renderView));
updatingCurrentLeftScrollPosition(currentRender.scrollWidth);
updateAllBlocksOnChartChangeWhileDragging(currentRender.scrollWidth);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider updating blocks for both scroll directions.

The updateAllBlocksOnChartChangeWhileDragging is only called during left scroll operations. This asymmetric behavior might lead to inconsistent block positioning when scrolling right.

Consider applying the same update for right scroll operations:

  } else if (side === "right") {
    updateCurrentView(view);
    updateRenderView(mergeRenderPayloads(renderView, currentRender.payload));
+   updateAllBlocksOnChartChangeWhileDragging(currentRender.scrollWidth);
    setItemsContainerWidth(itemsContainerWidth + currentRender.scrollWidth);

Committable suggestion was skipped due to low confidence.

setItemsContainerWidth(itemsContainerWidth + currentRender.scrollWidth);
} else if (side === "right") {
updateCurrentView(view);
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/gantt-chart/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const VIEWS_LIST: ChartDataType[] = [
startDate: new Date(),
currentDate: new Date(),
endDate: new Date(),
approxFilterRange: 18, // it will preview week starting dates all months data and there is 3 months limitation for preview ex: title (2, 9, 16, 23, 30)
approxFilterRange: 24, // it will preview week starting dates all months data and there is 3 months limitation for preview ex: title (2, 9, 16, 23, 30)
dayWidth: 5,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export const LeftResizable = observer((props: LeftResizableProps) => {
/>
<div
className={cn(
"absolute left-1 top-1/2 -translate-y-1/2 h-7 w-1 z-[5] rounded-sm bg-custom-background-100 transition-all duration-300",
"absolute left-1 top-1/2 -translate-y-1/2 h-7 w-1 z-[5] rounded-sm bg-custom-background-100 transition-all duration-300 opacity-0 group-hover:opacity-100",
{
"-left-1.5": isLeftResizing,
"-left-1.5 opacity-100": isLeftResizing,
}
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export const RightResizable = observer((props: RightResizableProps) => {
/>
<div
className={cn(
"absolute right-1 top-1/2 -translate-y-1/2 h-7 w-1 z-[5] rounded-sm bg-custom-background-100 transition-all duration-300",
"absolute right-1 top-1/2 -translate-y-1/2 h-7 w-1 z-[5] rounded-sm bg-custom-background-100 transition-all duration-300 opacity-0 group-hover:opacity-100",
{
"-right-1.5": isRightResizing,
"-right-1.5 opacity-100": isRightResizing,
}
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,16 @@ export const useGanttResizable = (

let width = initialPositionRef.current.width;
let marginLeft = initialPositionRef.current.marginLeft;
const blockRight = initialPositionRef.current.width + initialPositionRef.current.marginLeft;

if (dragDirection === "left") {
// calculate new marginLeft and update the initial marginLeft to the newly calculated one
marginLeft = Math.round(mouseX / dayWidth) * dayWidth;
// get Dimensions from dom's style
const prevMarginLeft = parseFloat(resizableDiv.style.transform.slice(11, -3));
const prevWidth = parseFloat(resizableDiv.style.width.slice(0, -2));
// calculate new width
width = blockRight - marginLeft;
const marginDelta = prevMarginLeft - marginLeft;
width = prevWidth + marginDelta;
Comment on lines +73 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve robustness of dimension retrieval logic.

The current implementation has potential fragility issues:

  1. Direct style string parsing with magic numbers (slice(11, -3) and slice(0, -2)) is brittle
  2. No error handling for missing or malformed style properties

Consider this safer implementation:

- const prevMarginLeft = parseFloat(resizableDiv.style.transform.slice(11, -3));
- const prevWidth = parseFloat(resizableDiv.style.width.slice(0, -2));
+ // Extract translateX value using regex
+ const transformMatch = resizableDiv.style.transform.match(/translateX\(([-\d.]+)px\)/);
+ const prevMarginLeft = transformMatch ? parseFloat(transformMatch[1]) : 0;
+ 
+ // Safely parse width
+ const prevWidth = resizableDiv.style.width ? parseFloat(resizableDiv.style.width) : 0;
+ 
+ // Validate parsed values
+ if (isNaN(prevMarginLeft) || isNaN(prevWidth)) {
+   console.warn('Invalid block dimensions detected');
+   return;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// get Dimensions from dom's style
const prevMarginLeft = parseFloat(resizableDiv.style.transform.slice(11, -3));
const prevWidth = parseFloat(resizableDiv.style.width.slice(0, -2));
// calculate new width
width = blockRight - marginLeft;
const marginDelta = prevMarginLeft - marginLeft;
width = prevWidth + marginDelta;
// get Dimensions from dom's style
// Extract translateX value using regex
const transformMatch = resizableDiv.style.transform.match(/translateX\(([-\d.]+)px\)/);
const prevMarginLeft = transformMatch ? parseFloat(transformMatch[1]) : 0;
// Safely parse width
const prevWidth = resizableDiv.style.width ? parseFloat(resizableDiv.style.width) : 0;
// Validate parsed values
if (isNaN(prevMarginLeft) || isNaN(prevWidth)) {
console.warn('Invalid block dimensions detected');
return;
}
// calculate new width
const marginDelta = prevMarginLeft - marginLeft;
width = prevWidth + marginDelta;

} else if (dragDirection === "right") {
// calculate new width and update the initialMarginLeft using +=
width = Math.round(mouseX / dayWidth) * dayWidth - marginLeft;
Expand Down
6 changes: 0 additions & 6 deletions web/core/components/gantt-chart/views/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,3 @@ export const getItemPositionWidth = (chartData: ChartDataType, itemData: IGanttB

return { marginLeft: scrollPosition, width: scrollWidth };
};

export const timelineViewHelpers = {
week: weekView,
month: monthView,
quarter: quarterView,
};
20 changes: 15 additions & 5 deletions web/core/components/gantt-chart/views/month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import uniqBy from "lodash/uniqBy";
//
import { months } from "../data";
import { ChartDataType } from "../types";
import { getNumberOfDaysInMonth } from "./helpers";
import { getNumberOfDaysBetweenTwoDates, getNumberOfDaysInMonth } from "./helpers";
import { getWeeksBetweenTwoDates, IWeekBlock } from "./week-view";

export interface IMonthBlock {
Expand Down Expand Up @@ -38,6 +38,9 @@ const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "
let minusDate: Date = new Date();
let plusDate: Date = new Date();

let startDate = new Date();
let endDate = new Date();

// if side is null generate months on both side of current date
if (side === null) {
const currentDate = renderState.data.currentDate;
Expand All @@ -47,12 +50,14 @@ const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "

if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate);

startDate = filteredDates.weeks[0]?.startDate;
endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate;
renderState = {
...renderState,
data: {
...renderState.data,
startDate: filteredDates.weeks[0]?.startDate,
endDate: filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate,
startDate,
endDate,
Comment on lines +53 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null safety checks for weeks array access.

The code assumes filteredDates.weeks array is not empty. Consider adding null safety checks to prevent potential runtime errors.

Apply this diff to add null safety:

-    startDate = filteredDates.weeks[0]?.startDate;
-    endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate;
+    startDate = filteredDates.weeks[0]?.startDate ?? minusDate;
+    endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate ?? plusDate;

Committable suggestion was skipped due to low confidence.

},
};
}
Expand All @@ -65,9 +70,11 @@ const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "

if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate);

startDate = filteredDates.weeks[0]?.startDate;
endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1);
renderState = {
...renderState,
data: { ...renderState.data, startDate: filteredDates.weeks[0].startDate },
data: { ...renderState.data, startDate },
};
}
// When side is right, generate more months on the right side of the end date
Expand All @@ -79,13 +86,16 @@ const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "

if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate);

startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1);
endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate;
renderState = {
...renderState,
data: { ...renderState.data, endDate: filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate },
};
}

const scrollWidth = filteredDates.weeks.length * monthPayload.data.dayWidth * 7;
const days = Math.abs(getNumberOfDaysBetweenTwoDates(startDate, endDate)) + 1;
const scrollWidth = days * monthPayload.data.dayWidth;

return { state: renderState, payload: filteredDates, scrollWidth: scrollWidth };
};
Expand Down
30 changes: 17 additions & 13 deletions web/core/components/gantt-chart/views/quarter-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left"
let minusDate: Date = new Date();
let plusDate: Date = new Date();

let startDate = new Date();
let endDate = new Date();

// if side is null generate months on both side of current date
if (side === null) {
const currentDate = renderState.data.currentDate;
Expand All @@ -38,53 +41,54 @@ const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left"

const startMonthBlock = filteredDates[0];
const endMonthBlock = filteredDates[filteredDates.length - 1];
startDate = new Date(startMonthBlock.year, startMonthBlock.month, 1);
endDate = new Date(endMonthBlock.year, endMonthBlock.month + 1, 0);

renderState = {
...renderState,
data: {
...renderState.data,
startDate: new Date(startMonthBlock.year, startMonthBlock.month, 1),
endDate: new Date(endMonthBlock.year, endMonthBlock.month + 1, 0),
startDate,
endDate,
},
};
}
// When side is left, generate more months on the left side of the start date
else if (side === "left") {
const currentDate = renderState.data.startDate;

minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1);
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range / 2, 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);

if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);

const startMonthBlock = filteredDates[0];
startDate = new Date(startMonthBlock.year, startMonthBlock.month, 1);
endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() - 1);
renderState = {
...renderState,
data: { ...renderState.data, startDate: new Date(startMonthBlock.year, startMonthBlock.month, 1) },
data: { ...renderState.data, startDate },
};
}
// When side is right, generate more months on the right side of the end date
else if (side === "right") {
const currentDate = renderState.data.endDate;

minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range / 2, 1);

if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);

const endMonthBlock = filteredDates[filteredDates.length - 1];

startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1);
endDate = new Date(endMonthBlock.year, endMonthBlock.month + 1, 0);
renderState = {
...renderState,
data: { ...renderState.data, endDate: new Date(endMonthBlock.year, endMonthBlock.month + 1, 0) },
data: { ...renderState.data, endDate },
};
}

const startMonthBlock = filteredDates[0];
const endMonthBlock = filteredDates[filteredDates.length - 1];
const startDate = new Date(startMonthBlock.year, startMonthBlock.month, 1);
const endDate = new Date(endMonthBlock.year, endMonthBlock.month + 1, 0);

const days = Math.abs(getNumberOfDaysBetweenTwoDates(startDate, endDate));
const days = Math.abs(getNumberOfDaysBetweenTwoDates(startDate, endDate)) + 1;
const scrollWidth = days * quarterPayload.data.dayWidth;

return { state: renderState, payload: filteredDates, scrollWidth: scrollWidth };
Expand Down
Loading