snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// components
|
||||
import type { IBlockUpdateData, IGanttBlock } from "@plane/types";
|
||||
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
|
||||
// hooks
|
||||
import { BlockRow } from "@/components/gantt-chart/blocks/block-row";
|
||||
import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants";
|
||||
import type { TSelectionHelper } from "@/hooks/use-multiple-select";
|
||||
// types
|
||||
|
||||
export type GanttChartBlocksProps = {
|
||||
blockIds: string[];
|
||||
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
|
||||
handleScrollToBlock: (block: IGanttBlock) => void;
|
||||
enableAddBlock: boolean | ((blockId: string) => boolean);
|
||||
showAllBlocks: boolean;
|
||||
selectionHelpers: TSelectionHelper;
|
||||
ganttContainerRef: React.RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
export function GanttChartRowList(props: GanttChartBlocksProps) {
|
||||
const {
|
||||
blockIds,
|
||||
blockUpdateHandler,
|
||||
handleScrollToBlock,
|
||||
enableAddBlock,
|
||||
showAllBlocks,
|
||||
selectionHelpers,
|
||||
ganttContainerRef,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="absolute top-0 left-0 w-max min-w-full">
|
||||
{blockIds?.map((blockId) => (
|
||||
<>
|
||||
<RenderIfVisible
|
||||
root={ganttContainerRef}
|
||||
horizontalOffset={100}
|
||||
verticalOffset={200}
|
||||
classNames="relative min-w-full w-max"
|
||||
placeholderChildren={<div className="pointer-events-none w-full" style={{ height: `${BLOCK_HEIGHT}px` }} />}
|
||||
shouldRecordHeights={false}
|
||||
>
|
||||
<BlockRow
|
||||
key={blockId}
|
||||
blockId={blockId}
|
||||
showAllBlocks={showAllBlocks}
|
||||
blockUpdateHandler={blockUpdateHandler}
|
||||
handleScrollToBlock={handleScrollToBlock}
|
||||
enableAddBlock={typeof enableAddBlock === "function" ? enableAddBlock(blockId) : enableAddBlock}
|
||||
selectionHelpers={selectionHelpers}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
/>
|
||||
</RenderIfVisible>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
121
apps/web/core/components/gantt-chart/blocks/block-row.tsx
Normal file
121
apps/web/core/components/gantt-chart/blocks/block-row.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ArrowNarrowRightOutline } from "@makeplane/propel/icons";
|
||||
// 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 | null>;
|
||||
};
|
||||
|
||||
export const BlockRow = observer(function BlockRow(props: 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 w-max min-w-full"
|
||||
onMouseEnter={() => updateActiveBlockId(blockId)}
|
||||
onMouseLeave={() => updateActiveBlockId(null)}
|
||||
style={{
|
||||
height: `${BLOCK_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cn("relative h-full bg-layer-transparent hover:bg-layer-transparent-hover", {
|
||||
"rounded-l-sm border border-r-0 border-accent-strong": getIsIssuePeeked(block.data.id),
|
||||
"bg-layer-transparent-hover": isBlockHoveredOn,
|
||||
"bg-accent-primary/5 hover:bg-accent-primary/10": isBlockSelected,
|
||||
"bg-accent-primary/10": isBlockSelected && isBlockHoveredOn,
|
||||
"border border-r-0 border-strong-1": 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-sm border border-strong bg-layer-1 text-secondary hover:text-primary"
|
||||
style={{
|
||||
left: `${SIDEBAR_WIDTH + 4}px`,
|
||||
}}
|
||||
onClick={() => handleScrollToBlock(block)}
|
||||
>
|
||||
<ArrowNarrowRightOutline
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"rotate-180": isBlockHiddenOnLeft,
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
: enableAddBlock && <ChartAddBlock block={block} blockUpdateHandler={blockUpdateHandler} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
111
apps/web/core/components/gantt-chart/blocks/block.tsx
Normal file
111
apps/web/core/components/gantt-chart/blocks/block.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
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 | null>;
|
||||
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
||||
};
|
||||
|
||||
export const GanttChartBlock = observer(function GanttChartBlock(props: 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 rounded-sm bg-layer-1" />}
|
||||
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>
|
||||
);
|
||||
});
|
||||
58
apps/web/core/components/gantt-chart/blocks/blocks-list.tsx
Normal file
58
apps/web/core/components/gantt-chart/blocks/blocks-list.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
//
|
||||
import type { IBlockUpdateDependencyData } from "@plane/types";
|
||||
import { GanttChartBlock } from "@/components/gantt-chart/blocks/block";
|
||||
|
||||
export type GanttChartBlocksProps = {
|
||||
blockIds: string[];
|
||||
blockToRender: (data: any) => React.ReactNode;
|
||||
enableBlockLeftResize: boolean | ((blockId: string) => boolean);
|
||||
enableBlockRightResize: boolean | ((blockId: string) => boolean);
|
||||
enableBlockMove: boolean | ((blockId: string) => boolean);
|
||||
ganttContainerRef: React.RefObject<HTMLDivElement | null>;
|
||||
showAllBlocks: boolean;
|
||||
updateBlockDates?: (updates: IBlockUpdateDependencyData[]) => Promise<void>;
|
||||
enableDependency: boolean | ((blockId: string) => boolean);
|
||||
};
|
||||
|
||||
export function GanttChartBlocksList(props: GanttChartBlocksProps) {
|
||||
const {
|
||||
blockIds,
|
||||
blockToRender,
|
||||
enableBlockLeftResize,
|
||||
enableBlockRightResize,
|
||||
enableBlockMove,
|
||||
ganttContainerRef,
|
||||
showAllBlocks,
|
||||
updateBlockDates,
|
||||
enableDependency,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{blockIds?.map((blockId) => (
|
||||
<GanttChartBlock
|
||||
key={blockId}
|
||||
blockId={blockId}
|
||||
showAllBlocks={showAllBlocks}
|
||||
blockToRender={blockToRender}
|
||||
enableBlockLeftResize={
|
||||
typeof enableBlockLeftResize === "function" ? enableBlockLeftResize(blockId) : enableBlockLeftResize
|
||||
}
|
||||
enableBlockRightResize={
|
||||
typeof enableBlockRightResize === "function" ? enableBlockRightResize(blockId) : enableBlockRightResize
|
||||
}
|
||||
enableBlockMove={typeof enableBlockMove === "function" ? enableBlockMove(blockId) : enableBlockMove}
|
||||
enableDependency={typeof enableDependency === "function" ? enableDependency(blockId) : enableDependency}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
updateBlockDates={updateBlockDates}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user