feat: init
This commit is contained in:
115
apps/web/core/components/gantt-chart/blocks/block-row.tsx
Normal file
115
apps/web/core/components/gantt-chart/blocks/block-row.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
// helpers
|
||||
import type { IBlockUpdateData, IGanttBlock } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
|
||||
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
|
||||
//
|
||||
import { BLOCK_HEIGHT, SIDEBAR_WIDTH } from "../constants";
|
||||
import { ChartAddBlock } from "../helpers";
|
||||
|
||||
type Props = {
|
||||
blockId: string;
|
||||
showAllBlocks: boolean;
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
handleScrollToBlock: (block: IGanttBlock) => void;
|
||||
enableAddBlock: boolean;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
ganttContainerRef: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export const BlockRow: React.FC<Props> = observer((props) => {
|
||||
const { blockId, showAllBlocks, blockUpdateHandler, handleScrollToBlock, enableAddBlock, selectionHelpers } = props;
|
||||
// states
|
||||
const [isHidden, setIsHidden] = useState(false);
|
||||
const [isBlockHiddenOnLeft, setIsBlockHiddenOnLeft] = useState(false);
|
||||
// store hooks
|
||||
const { getBlockById, updateActiveBlockId, isBlockActive } = useTimeLineChartStore();
|
||||
const { getIsIssuePeeked } = useIssueDetail();
|
||||
|
||||
const block = getBlockById(blockId);
|
||||
|
||||
useEffect(() => {
|
||||
const intersectionRoot = document.querySelector("#gantt-container") as HTMLDivElement;
|
||||
const timelineBlock = document.getElementById(`gantt-block-${block?.id}`);
|
||||
if (!timelineBlock || !intersectionRoot) return;
|
||||
|
||||
setIsBlockHiddenOnLeft(
|
||||
!!block.position?.marginLeft &&
|
||||
!!block.position?.width &&
|
||||
intersectionRoot.scrollLeft > block.position.marginLeft + block.position.width
|
||||
);
|
||||
|
||||
// Observe if the block is visible on the chart
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
setIsHidden(!entry.isIntersecting);
|
||||
setIsBlockHiddenOnLeft(entry.boundingClientRect.right < (entry.rootBounds?.left ?? 0));
|
||||
});
|
||||
},
|
||||
{
|
||||
root: intersectionRoot,
|
||||
rootMargin: `0px 0px 0px -${SIDEBAR_WIDTH}px`,
|
||||
}
|
||||
);
|
||||
|
||||
observer.observe(timelineBlock);
|
||||
|
||||
return () => {
|
||||
observer.unobserve(timelineBlock);
|
||||
};
|
||||
}, [block]);
|
||||
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!block || !block.data || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
|
||||
|
||||
const isBlockVisibleOnChart = block.start_date || block.target_date;
|
||||
const isBlockSelected = selectionHelpers.getIsEntitySelected(block.id);
|
||||
const isBlockFocused = selectionHelpers.getIsEntityActive(block.id);
|
||||
const isBlockHoveredOn = isBlockActive(block.id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative min-w-full w-max"
|
||||
onMouseEnter={() => updateActiveBlockId(blockId)}
|
||||
onMouseLeave={() => updateActiveBlockId(null)}
|
||||
style={{
|
||||
height: `${BLOCK_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn("relative h-full", {
|
||||
"rounded-l border border-r-0 border-custom-primary-70": getIsIssuePeeked(block.data.id),
|
||||
"bg-custom-background-90": isBlockHoveredOn,
|
||||
"bg-custom-primary-100/5 hover:bg-custom-primary-100/10": isBlockSelected,
|
||||
"bg-custom-primary-100/10": isBlockSelected && isBlockHoveredOn,
|
||||
"border border-r-0 border-custom-border-400": isBlockFocused,
|
||||
})}
|
||||
>
|
||||
{isBlockVisibleOnChart
|
||||
? isHidden && (
|
||||
<button
|
||||
type="button"
|
||||
className="sticky z-[5] grid h-8 w-8 translate-y-1.5 cursor-pointer place-items-center rounded border border-custom-border-300 bg-custom-background-80 text-custom-text-200 hover:text-custom-text-100"
|
||||
style={{
|
||||
left: `${SIDEBAR_WIDTH + 4}px`,
|
||||
}}
|
||||
onClick={() => handleScrollToBlock(block)}
|
||||
>
|
||||
<ArrowRight
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"rotate-180": isBlockHiddenOnLeft,
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
: enableAddBlock && <ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
105
apps/web/core/components/gantt-chart/blocks/block.tsx
Normal file
105
apps/web/core/components/gantt-chart/blocks/block.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { RefObject } from "react";
|
||||
import { useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import type { IBlockUpdateDependencyData } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
|
||||
// helpers
|
||||
// hooks
|
||||
import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
|
||||
// constants
|
||||
import { BLOCK_HEIGHT } from "../constants";
|
||||
// components
|
||||
import { ChartDraggable } from "../helpers";
|
||||
import { useGanttResizable } from "../helpers/blockResizables/use-gantt-resizable";
|
||||
|
||||
type Props = {
|
||||
blockId: string;
|
||||
showAllBlocks: boolean;
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
enableBlockLeftResize: boolean;
|
||||
enableBlockRightResize: boolean;
|
||||
enableBlockMove: boolean;
|
||||
enableDependency: boolean;
|
||||
ganttContainerRef: RefObject<HTMLDivElement>;
|
||||
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
||||
};
|
||||
|
||||
export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
blockId,
|
||||
showAllBlocks,
|
||||
blockToRender,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
ganttContainerRef,
|
||||
enableDependency,
|
||||
updateBlockDates,
|
||||
} = props;
|
||||
// store hooks
|
||||
const { updateActiveBlockId, getBlockById, getIsCurrentDependencyDragging, currentView } = useTimeLineChartStore();
|
||||
// refs
|
||||
const resizableRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const block = getBlockById(blockId);
|
||||
|
||||
const isCurrentDependencyDragging = getIsCurrentDependencyDragging(blockId);
|
||||
|
||||
const { isMoving, handleBlockDrag } = useGanttResizable(block, resizableRef, ganttContainerRef, updateBlockDates);
|
||||
|
||||
const isBlockVisibleOnChart = block?.start_date || block?.target_date;
|
||||
const isBlockComplete = block?.start_date && block?.target_date;
|
||||
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!block || (!showAllBlocks && !isBlockVisibleOnChart)) return null;
|
||||
|
||||
if (!block.data) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("relative z-[5]", {
|
||||
"transition-all": !!isMoving && currentView === "week",
|
||||
"pointer-events-none": !isBlockVisibleOnChart,
|
||||
})}
|
||||
id={`gantt-block-${block.id}`}
|
||||
ref={resizableRef}
|
||||
style={{
|
||||
height: `${BLOCK_HEIGHT}px`,
|
||||
marginLeft: `${block.position?.marginLeft}px`,
|
||||
width: `${block.position?.width}px`,
|
||||
}}
|
||||
>
|
||||
{isBlockVisibleOnChart && (
|
||||
<RenderIfVisible
|
||||
root={ganttContainerRef}
|
||||
horizontalOffset={100}
|
||||
verticalOffset={200}
|
||||
classNames="flex h-full w-full items-center"
|
||||
placeholderChildren={<div className="h-8 w-full bg-custom-background-80 rounded" />}
|
||||
shouldRecordHeights={false}
|
||||
forceRender={isCurrentDependencyDragging}
|
||||
>
|
||||
<div
|
||||
className={cn("relative h-full w-full")}
|
||||
onMouseEnter={() => updateActiveBlockId(blockId)}
|
||||
onMouseLeave={() => updateActiveBlockId(null)}
|
||||
>
|
||||
<ChartDraggable
|
||||
block={block}
|
||||
blockToRender={blockToRender}
|
||||
handleBlockDrag={handleBlockDrag}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove && !!isBlockComplete}
|
||||
enableDependency={enableDependency}
|
||||
isMoving={isMoving}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
/>
|
||||
</div>
|
||||
</RenderIfVisible>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user