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

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export { BaseGanttLayout } from "./layout";
export { BaseGanttSidebar } from "./sidebar";

View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useCallback, useMemo } from "react";
import { observer } from "mobx-react";
import { GANTT_TIMELINE_TYPE } from "@plane/types";
import type {
IBaseLayoutsGanttItem,
IBaseLayoutsGanttProps,
TGanttBlockUpdateData,
IBlockUpdateDependencyData,
} from "@plane/types";
import { cn } from "@plane/utils";
import { TimeLineTypeContext } from "@/components/gantt-chart/contexts";
import { GanttChartRoot } from "@/components/gantt-chart/root";
import { BaseGanttSidebar } from "./sidebar";
export const BaseGanttLayout = observer(function BaseGanttLayout<T extends IBaseLayoutsGanttItem>(
props: IBaseLayoutsGanttProps<T>
) {
const {
items,
groupedItemIds,
groups,
renderBlock,
renderSidebar,
onBlockUpdate,
onDateUpdate,
enableBlockLeftResize = false,
enableBlockRightResize = false,
enableBlockMove = false,
enableReorder = false,
enableAddBlock = false,
enableSelection = false,
enableDependency = false,
showAllBlocks = false,
showToday = true,
border = false,
title = "Items",
loaderTitle = "items",
quickAdd,
loadMoreItems,
isLoading: _isLoading,
className,
timelineType: timelineTypeKey = GANTT_TIMELINE_TYPE.ISSUE,
} = props;
// Flatten all grouped item IDs into a single array for gantt
// Gantt doesn't typically show groups, it shows all items on a timeline
const blockIds = useMemo(() => {
const allIds: string[] = [];
groups.forEach((group) => {
const itemIds = groupedItemIds[group.id] || [];
allIds.push(...itemIds);
});
return allIds;
}, [groups, groupedItemIds]);
// Block update handler - transforms base layout item updates to gantt block updates
const handleBlockUpdate = useCallback(
(block: T, payload: TGanttBlockUpdateData) => {
if (onBlockUpdate) {
onBlockUpdate(block, payload);
}
},
[onBlockUpdate]
);
// Block renderer - wraps the user's render function
const blockToRender = useCallback((item: T) => renderBlock(item), [renderBlock]);
// Sidebar renderer - uses custom or default
const sidebarToRender = useCallback(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(sidebarProps: any) => {
if (renderSidebar) {
// If custom sidebar renderer provided, use it
return (
<BaseGanttSidebar {...sidebarProps} items={items} renderItem={renderSidebar} loadMoreItems={loadMoreItems} />
);
}
// Otherwise use default sidebar
return (
<BaseGanttSidebar {...sidebarProps} items={items} renderItem={renderBlock} loadMoreItems={loadMoreItems} />
);
},
[renderSidebar, renderBlock, items, loadMoreItems]
);
const timelineType = GANTT_TIMELINE_TYPE[timelineTypeKey];
// Date update handler - transforms IBlockUpdateDependencyData to TGanttDateUpdate
const handleDateUpdate = useCallback(
async (updates: IBlockUpdateDependencyData[]) => {
if (onDateUpdate) {
// Transform IBlockUpdateDependencyData[] to TGanttDateUpdate[]
const transformedUpdates = updates.map((update) => ({
id: update.id,
start_date: update.start_date,
target_date: update.target_date,
}));
await onDateUpdate(transformedUpdates);
}
},
[onDateUpdate]
);
// Load more handler - wraps loadMoreItems to match expected signature
const handleLoadMore = useCallback(() => {
if (loadMoreItems) {
loadMoreItems(""); // Pass empty string as default group ID
}
}, [loadMoreItems]);
return (
<TimeLineTypeContext.Provider value={timelineType}>
<div className={cn("h-full w-full", className)}>
<GanttChartRoot
border={border}
title={title}
loaderTitle={loaderTitle}
blockIds={blockIds}
blockUpdateHandler={handleBlockUpdate}
blockToRender={blockToRender}
sidebarToRender={sidebarToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}
enableBlockMove={enableBlockMove}
enableReorder={enableReorder}
enableAddBlock={enableAddBlock}
enableSelection={enableSelection}
enableDependency={enableDependency}
showAllBlocks={showAllBlocks}
showToday={showToday}
quickAdd={quickAdd}
updateBlockDates={onDateUpdate ? handleDateUpdate : undefined}
loadMoreBlocks={loadMoreItems ? handleLoadMore : undefined}
canLoadMoreBlocks={!!loadMoreItems} // Enable pagination if loadMoreItems is provided
/>
</div>
</TimeLineTypeContext.Provider>
);
});

View File

@@ -0,0 +1,155 @@
/**
* 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 { useState } from "react";
import { observer } from "mobx-react";
import type { IBaseLayoutsBaseItem, IBlockUpdateData } from "@plane/types";
import { Loader, Row } from "@plane/ui";
import { cn } from "@plane/utils";
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants";
import { GanttDnDHOC } from "@/components/gantt-chart/sidebar/gantt-dnd-HOC";
import { handleOrderChange } from "@/components/gantt-chart/sidebar/utils";
import { GanttLayoutListItemLoader } from "@/components/ui/loader/layouts/gantt-layout-loader";
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
type Props<T extends IBaseLayoutsBaseItem> = {
blockUpdateHandler: (block: T, payload: IBlockUpdateData) => void;
canLoadMoreBlocks?: boolean;
loadMoreItems?: (groupId: string) => void;
ganttContainerRef: RefObject<HTMLDivElement | null>;
blockIds: string[];
enableReorder: boolean;
showAllBlocks?: boolean;
items: Record<string, T>;
renderItem: (item: T) => React.ReactNode;
};
export const BaseGanttSidebar = observer(function BaseGanttSidebar<T extends IBaseLayoutsBaseItem>(props: Props<T>) {
const {
blockUpdateHandler,
blockIds,
enableReorder,
loadMoreItems,
canLoadMoreBlocks,
ganttContainerRef,
showAllBlocks = false,
items,
renderItem,
} = props;
const { getBlockById, updateActiveBlockId, isBlockActive, getNumberOfDaysFromPosition } = useTimeLineChartStore();
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
const isPaginating = false; // TODO: Add proper pagination state
useIntersectionObserver(
ganttContainerRef,
isPaginating ? null : intersectionElement,
loadMoreItems ? () => loadMoreItems("") : undefined,
"100% 0% 100% 0%"
);
const handleOnDrop = (
draggingBlockId: string | undefined,
droppedBlockId: string | undefined,
dropAtEndOfList: boolean
) => {
handleOrderChange(draggingBlockId, droppedBlockId, dropAtEndOfList, blockIds, getBlockById, blockUpdateHandler);
};
return (
<div>
{blockIds ? (
<>
{blockIds.map((blockId, index) => {
const block = getBlockById(blockId);
const item = items[blockId];
const isBlockVisibleOnSidebar = 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 && !isBlockVisibleOnSidebar)) return null;
if (!item) return null;
return (
<RenderIfVisible
key={blockId}
root={ganttContainerRef}
horizontalOffset={100}
verticalOffset={200}
shouldRecordHeights={false}
placeholderChildren={<GanttLayoutListItemLoader />}
>
<GanttDnDHOC
id={blockId}
isLastChild={index === blockIds.length - 1}
isDragEnabled={enableReorder}
onDrop={handleOnDrop}
>
{(isDragging: boolean) => {
const block = getBlockById(blockId);
const isBlockComplete = !!block?.start_date && !!block?.target_date;
const duration = isBlockComplete ? getNumberOfDaysFromPosition(block?.position?.width) : undefined;
const isBlockHoveredOn = isBlockActive(blockId);
return (
<div
className={cn("group/list-block", {
"rounded-sm bg-layer-1": isDragging,
})}
onMouseEnter={() => updateActiveBlockId(blockId)}
onMouseLeave={() => updateActiveBlockId(null)}
>
<Row
className={cn(
"group flex w-full items-center gap-2 bg-layer-transparent pr-4 hover:bg-layer-transparent-hover",
{
"bg-layer-transparent-hover": isBlockHoveredOn,
}
)}
style={{
height: `${BLOCK_HEIGHT}px`,
}}
>
<div className="flex h-full flex-grow items-center justify-between gap-2 truncate">
<div className="flex-grow truncate">{renderItem(item)}</div>
{duration && (
<div className="flex-shrink-0 text-13 text-secondary">
<span>
{duration} day{duration > 1 ? "s" : ""}
</span>
</div>
)}
</div>
</Row>
</div>
);
}}
</GanttDnDHOC>
</RenderIfVisible>
);
})}
{canLoadMoreBlocks && (
<div ref={setIntersectionElement} className="p-2">
<div className="flex h-10 w-full animate-pulse items-center justify-between gap-1.5 rounded-sm bg-layer-1 px-4 py-1.5 md:h-8 md:px-1" />
</div>
)}
</>
) : (
<Loader className="space-y-3 pr-2">
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
</Loader>
)}
</div>
);
});