From 5ca137fb9202180c461f7f4efaa3d01603c19e3e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 31 Jan 2024 17:47:03 +0530 Subject: [PATCH] chore: gantt sidebar and main content scroll sync --- .../gantt-chart/blocks/blocks-display.tsx | 63 ++++++++++++------- web/components/gantt-chart/chart/index.tsx | 34 +++++++--- web/components/gantt-chart/contexts/index.tsx | 15 ++--- .../gantt-chart/helpers/draggable.tsx | 6 +- web/components/gantt-chart/root.tsx | 2 +- .../gantt-chart/sidebar/sidebar.tsx | 11 +--- web/components/gantt-chart/types/index.ts | 2 + .../issue-layouts/gantt/base-gantt-root.tsx | 4 +- .../issues/issue-layouts/gantt/blocks.tsx | 17 +++-- 9 files changed, 100 insertions(+), 54 deletions(-) diff --git a/web/components/gantt-chart/blocks/blocks-display.tsx b/web/components/gantt-chart/blocks/blocks-display.tsx index e13be116b..78b2607d9 100644 --- a/web/components/gantt-chart/blocks/blocks-display.tsx +++ b/web/components/gantt-chart/blocks/blocks-display.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, useEffect, useRef } from "react"; // hooks import { useIssueDetail } from "hooks/store"; import { useChart } from "../hooks"; @@ -12,7 +12,7 @@ import { IBlockUpdateData, IGanttBlock } from "../types"; export type GanttChartBlocksProps = { itemsContainerWidth: number; blocks: IGanttBlock[] | null; - blockToRender: (data: any) => React.ReactNode; + blockToRender: (data: any, textDisplacement: number) => React.ReactNode; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; enableBlockLeftResize: boolean; enableBlockRightResize: boolean; @@ -31,9 +31,12 @@ export const GanttChartBlocks: FC = (props) => { enableBlockMove, showAllBlocks, } = props; - - const { activeBlock, dispatch } = useChart(); + // refs + const blocksContainerRef = useRef(null); + // store hooks const { peekIssue } = useIssueDetail(); + // chart hook + const { activeBlock, dispatch, scrollTop, updateScrollTop } = useChart(); // update the active block on hover const updateActiveBlock = (block: IGanttBlock | null) => { @@ -75,10 +78,28 @@ export const GanttChartBlocks: FC = (props) => { }); }; + const handleBlocksScroll = (e: React.UIEvent) => { + updateScrollTop(e.currentTarget.scrollTop); + + const sidebarScrollContainer = document.getElementById("gantt-sidebar-scroll-container") as HTMLDivElement; + if (!sidebarScrollContainer) return; + + sidebarScrollContainer.scrollTop = e.currentTarget.scrollTop; + }; + + useEffect(() => { + const blocksContainer = blocksContainerRef.current; + if (!blocksContainer) return; + + blocksContainer.scrollTop = scrollTop; + }, [scrollTop]); + return (
{blocks && blocks.length > 0 && @@ -91,26 +112,26 @@ export const GanttChartBlocks: FC = (props) => { return (
updateActiveBlock(block)} onMouseLeave={() => updateActiveBlock(null)} > - {!isBlockVisibleOnChart && } - handleChartBlockPosition(block, ...args)} - enableBlockLeftResize={enableBlockLeftResize} - enableBlockRightResize={enableBlockRightResize} - enableBlockMove={enableBlockMove} - /> + {isBlockVisibleOnChart ? ( + handleChartBlockPosition(block, ...args)} + enableBlockLeftResize={enableBlockLeftResize} + enableBlockRightResize={enableBlockRightResize} + enableBlockMove={enableBlockMove} + /> + ) : ( + + )}
); })} diff --git a/web/components/gantt-chart/chart/index.tsx b/web/components/gantt-chart/chart/index.tsx index 4592bfb5b..869f5f81b 100644 --- a/web/components/gantt-chart/chart/index.tsx +++ b/web/components/gantt-chart/chart/index.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from "react"; +import { FC, useEffect, useRef, useState } from "react"; // icons // components import { GanttChartBlocks } from "components/gantt-chart"; @@ -39,7 +39,7 @@ type ChartViewRootProps = { loaderTitle: string; blocks: IGanttBlock[] | null; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; - blockToRender: (data: any) => React.ReactNode; + blockToRender: (data: any, textDisplacement: number) => React.ReactNode; sidebarToRender: (props: any) => React.ReactNode; enableBlockLeftResize: boolean; enableBlockRightResize: boolean; @@ -69,8 +69,11 @@ export const ChartViewRoot: FC = (props) => { const [itemsContainerWidth, setItemsContainerWidth] = useState(0); const [fullScreenMode, setFullScreenMode] = useState(false); const [chartBlocks, setChartBlocks] = useState(null); // blocks state management starts + // refs + const sidebarRef = useRef(null); // hooks - const { currentView, currentViewData, renderView, dispatch, allViews, updateScrollLeft } = useChart(); + const { currentView, currentViewData, renderView, dispatch, allViews, updateScrollLeft, scrollTop, updateScrollTop } = + useChart(); const renderBlockStructure = (view: any, blocks: IGanttBlock[] | null) => blocks && blocks.length > 0 @@ -202,17 +205,25 @@ export const ChartViewRoot: FC = (props) => { const scrollWidth: number = scrollContainer?.scrollWidth; const clientVisibleWidth: number = scrollContainer?.clientWidth; - const currentScrollPosition: number = scrollContainer?.scrollLeft; + const currentLeftScrollPosition: number = scrollContainer?.scrollLeft; - updateScrollLeft(currentScrollPosition); + updateScrollLeft(currentLeftScrollPosition); const approxRangeLeft: number = scrollWidth >= clientVisibleWidth + 1000 ? 1000 : scrollWidth - clientVisibleWidth; const approxRangeRight: number = scrollWidth - (approxRangeLeft + clientVisibleWidth); - if (currentScrollPosition >= approxRangeRight) updateCurrentViewRenderPayload("right", currentView); - if (currentScrollPosition <= approxRangeLeft) updateCurrentViewRenderPayload("left", currentView); + if (currentLeftScrollPosition >= approxRangeRight) updateCurrentViewRenderPayload("right", currentView); + if (currentLeftScrollPosition <= approxRangeLeft) updateCurrentViewRenderPayload("left", currentView); }; + const onSidebarScroll = (e: React.UIEvent) => updateScrollTop(e.currentTarget.scrollTop); + + // useEffect(() => { + // const sidebarContainer = sidebarRef.current; + // if (!sidebarContainer) return; + // sidebarContainer.scrollTop = scrollTop; + // }, [scrollTop]); + return (
= (props) => {
Duration
- {sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })} +
+ {sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })} +
= ({ children }) => { + // states; const [state, dispatch] = useState({ currentView: initialView, currentViewData: currentViewDataWithView(initialView), @@ -31,23 +32,23 @@ export const ChartContextProvider: React.FC<{ children: React.ReactNode }> = ({ allViews: allViewsWithData, activeBlock: null, }); - const [scrollLeft, setScrollLeft] = useState(0); + const [scrollTop, setScrollTop] = useState(0); const handleDispatch = (action: ChartContextActionPayload): ChartContextData => { const newState = chartReducer(state, action); - dispatch(() => newState); - return newState; }; - const updateScrollLeft = (scrollLeft: number) => { - setScrollLeft(scrollLeft); - }; + const updateScrollLeft = (scrollLeft: number) => setScrollLeft(scrollLeft); + + const updateScrollTop = (scrollTop: number) => setScrollTop(scrollTop); return ( - + {children} ); diff --git a/web/components/gantt-chart/helpers/draggable.tsx b/web/components/gantt-chart/helpers/draggable.tsx index d2c4448bb..81d006f41 100644 --- a/web/components/gantt-chart/helpers/draggable.tsx +++ b/web/components/gantt-chart/helpers/draggable.tsx @@ -7,7 +7,7 @@ import { IGanttBlock } from "../types"; type Props = { block: IGanttBlock; - blockToRender: (data: any) => React.ReactNode; + blockToRender: (data: any, textDisplacement: number) => React.ReactNode; handleBlock: (totalBlockShifts: number, dragDirection: "left" | "right" | "move") => void; enableBlockLeftResize: boolean; enableBlockRightResize: boolean; @@ -223,6 +223,8 @@ export const ChartDraggable: React.FC = (props) => { scrollLeft > block.position.marginLeft + block.position.width; const isBlockHiddenOnRight = posFromLeft && window && posFromLeft > window.innerWidth; + const textDisplacement = scrollLeft - (block.position?.marginLeft ?? 0); + return ( <> {/* move to left side hidden block button */} @@ -272,7 +274,7 @@ export const ChartDraggable: React.FC = (props) => { className={`relative z-[2] flex h-8 w-full items-center rounded ${isMoving ? "pointer-events-none" : ""}`} onMouseDown={handleBlockMove} > - {blockToRender(block.data)} + {blockToRender(block.data, textDisplacement)}
{/* right resize drag handle */} {enableBlockRightResize && ( diff --git a/web/components/gantt-chart/root.tsx b/web/components/gantt-chart/root.tsx index 7673da88e..0f4a948ad 100644 --- a/web/components/gantt-chart/root.tsx +++ b/web/components/gantt-chart/root.tsx @@ -12,7 +12,7 @@ type GanttChartRootProps = { loaderTitle: string; blocks: IGanttBlock[] | null; blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void; - blockToRender: (data: any) => React.ReactNode; + blockToRender: (data: any, textDisplacement: number) => React.ReactNode; sidebarToRender: (props: any) => React.ReactNode; enableBlockLeftResize?: boolean; enableBlockRightResize?: boolean; diff --git a/web/components/gantt-chart/sidebar/sidebar.tsx b/web/components/gantt-chart/sidebar/sidebar.tsx index bca39a0bd..f4d06c8a3 100644 --- a/web/components/gantt-chart/sidebar/sidebar.tsx +++ b/web/components/gantt-chart/sidebar/sidebar.tsx @@ -1,4 +1,3 @@ -import { useRouter } from "next/router"; import { DragDropContext, Draggable, Droppable, DropResult } from "@hello-pangea/dnd"; import { MoreVertical } from "lucide-react"; // hooks @@ -43,9 +42,6 @@ export const IssueGanttSidebar: React.FC = (props) => { showAllBlocks = false, } = props; - const router = useRouter(); - const { cycleId } = router.query; - const { activeBlock, dispatch } = useChart(); const { peekIssue } = useIssueDetail(); @@ -105,12 +101,7 @@ export const IssueGanttSidebar: React.FC = (props) => { {(droppableProvided) => ( -
+
<> {blocks ? ( blocks.map((block, index) => { diff --git a/web/components/gantt-chart/types/index.ts b/web/components/gantt-chart/types/index.ts index 1360f9f45..f9f18fddb 100644 --- a/web/components/gantt-chart/types/index.ts +++ b/web/components/gantt-chart/types/index.ts @@ -54,6 +54,8 @@ export type ChartContextActionPayload = export interface ChartContextReducer extends ChartContextData { scrollLeft: number; updateScrollLeft: (scrollLeft: number) => void; + scrollTop: number; + updateScrollTop: (scrollTop: number) => void; dispatch: (action: ChartContextActionPayload) => void; } diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 601205b5c..c8857dcc8 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -69,7 +69,9 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan loaderTitle="Issues" blocks={issues ? renderIssueBlocksStructure(issues as TIssue[]) : null} blockUpdateHandler={updateIssueBlockStructure} - blockToRender={(data: TIssue) => } + blockToRender={(data: TIssue, textDisplacement) => ( + + )} sidebarToRender={(props) => ( { - // hooks +export const IssueGanttBlock = ({ data, textDisplacement }: { data: TIssue; textDisplacement: number }) => { + // store hooks const { router: { workspaceSlug }, } = useApplication(); @@ -43,7 +44,15 @@ export const IssueGanttBlock = ({ data }: { data: TIssue }) => { } position="top-left" > -
{data?.name}
+
+ 0 ? { paddingLeft: `${textDisplacement}px` } : {}), + }} + > + {data?.name} + +
);