mirror of
https://github.com/makeplane/plane.git
synced 2025-12-15 19:37:51 +01:00
* Add github action to codespell preview on push and PRs
* Add rudimentary codespell config
* [DATALAD RUNCMD] chore: run codespell throughout fixing a few typos interactively
=== Do not change lines below ===
{
"chain": [],
"cmd": "codespell -w -i 3 -C 4 ./apps/web/core/components/workspace/delete-workspace-form.tsx ./deployments/cli/community/README.md",
"exit": 0,
"extra_inputs": [],
"inputs": [],
"outputs": [],
"pwd": "."
}
^^^ Do not change lines above ^^^
* Adjust coespell regex to ignore all camelCased words
* [DATALAD RUNCMD] chore: run codespell throughout fixing a few new typos automagically
=== Do not change lines below ===
{
"chain": [],
"cmd": "codespell -w",
"exit": 0,
"extra_inputs": [],
"inputs": [],
"outputs": [],
"pwd": "."
}
^^^ Do not change lines above ^^^
341 lines
10 KiB
TypeScript
341 lines
10 KiB
TypeScript
import { isEqual, set } from "lodash-es";
|
|
import { action, makeObservable, observable, runInAction } from "mobx";
|
|
import { computedFn } from "mobx-utils";
|
|
// components
|
|
import type {
|
|
ChartDataType,
|
|
IBlockUpdateDependencyData,
|
|
IGanttBlock,
|
|
TGanttViews,
|
|
EGanttBlockType,
|
|
} from "@plane/types";
|
|
import { renderFormattedPayloadDate } from "@plane/utils";
|
|
import { currentViewDataWithView } from "@/components/gantt-chart/data";
|
|
import {
|
|
getDateFromPositionOnGantt,
|
|
getItemPositionWidth,
|
|
getPositionFromDate,
|
|
} from "@/components/gantt-chart/views/helpers";
|
|
// helpers
|
|
// store
|
|
import type { RootStore } from "@/plane-web/store/root.store";
|
|
|
|
// types
|
|
type BlockData = {
|
|
id: string;
|
|
name: string;
|
|
sort_order: number | null;
|
|
start_date?: string | undefined | null;
|
|
target_date?: string | undefined | null;
|
|
project_id?: string | undefined | null;
|
|
};
|
|
|
|
export interface IBaseTimelineStore {
|
|
// observables
|
|
currentView: TGanttViews;
|
|
currentViewData: ChartDataType | undefined;
|
|
activeBlockId: string | null;
|
|
renderView: any;
|
|
isDragging: boolean;
|
|
isDependencyEnabled: boolean;
|
|
//
|
|
setBlockIds: (ids: string[]) => void;
|
|
getBlockById: (blockId: string) => IGanttBlock;
|
|
// computed functions
|
|
getIsCurrentDependencyDragging: (blockId: string) => boolean;
|
|
isBlockActive: (blockId: string) => boolean;
|
|
// actions
|
|
updateCurrentView: (view: TGanttViews) => void;
|
|
updateCurrentViewData: (data: ChartDataType | undefined) => void;
|
|
updateActiveBlockId: (blockId: string | null) => void;
|
|
updateRenderView: (data: any) => void;
|
|
updateAllBlocksOnChartChangeWhileDragging: (addedWidth: number) => void;
|
|
getUpdatedPositionAfterDrag: (
|
|
id: string,
|
|
shouldUpdateHalfBlock: boolean,
|
|
ignoreDependencies?: boolean
|
|
) => IBlockUpdateDependencyData[];
|
|
updateBlockPosition: (id: string, deltaLeft: number, deltaWidth: number, ignoreDependencies?: boolean) => void;
|
|
getNumberOfDaysFromPosition: (position: number | undefined) => number | undefined;
|
|
setIsDragging: (isDragging: boolean) => void;
|
|
initGantt: () => void;
|
|
|
|
getDateFromPositionOnGantt: (position: number, offsetDays: number) => Date | undefined;
|
|
getPositionFromDateOnGantt: (date: string | Date, offSetWidth: number) => number | undefined;
|
|
}
|
|
|
|
export class BaseTimeLineStore implements IBaseTimelineStore {
|
|
blocksMap: Record<string, IGanttBlock> = {};
|
|
blockIds: string[] | undefined = undefined;
|
|
|
|
isDragging: boolean = false;
|
|
currentView: TGanttViews = "week";
|
|
currentViewData: ChartDataType | undefined = undefined;
|
|
activeBlockId: string | null = null;
|
|
renderView: any = [];
|
|
|
|
rootStore: RootStore;
|
|
|
|
isDependencyEnabled = false;
|
|
|
|
constructor(_rootStore: RootStore) {
|
|
makeObservable(this, {
|
|
// observables
|
|
blocksMap: observable,
|
|
blockIds: observable,
|
|
isDragging: observable.ref,
|
|
currentView: observable.ref,
|
|
currentViewData: observable,
|
|
activeBlockId: observable.ref,
|
|
renderView: observable,
|
|
// actions
|
|
setIsDragging: action,
|
|
setBlockIds: action.bound,
|
|
initGantt: action.bound,
|
|
updateCurrentView: action.bound,
|
|
updateCurrentViewData: action.bound,
|
|
updateActiveBlockId: action.bound,
|
|
updateRenderView: action.bound,
|
|
});
|
|
|
|
this.initGantt();
|
|
|
|
this.rootStore = _rootStore;
|
|
}
|
|
|
|
/**
|
|
* Update Block Ids to derive blocks from
|
|
* @param ids
|
|
*/
|
|
setBlockIds = (ids: string[]) => {
|
|
this.blockIds = ids;
|
|
};
|
|
|
|
/**
|
|
* setIsDragging
|
|
* @param isDragging
|
|
*/
|
|
setIsDragging = (isDragging: boolean) => {
|
|
runInAction(() => {
|
|
this.isDragging = isDragging;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @description check if block is active
|
|
* @param {string} blockId
|
|
*/
|
|
isBlockActive = computedFn((blockId: string): boolean => this.activeBlockId === blockId);
|
|
|
|
/**
|
|
* @description update current view
|
|
* @param {TGanttViews} view
|
|
*/
|
|
updateCurrentView = (view: TGanttViews) => {
|
|
this.currentView = view;
|
|
};
|
|
|
|
/**
|
|
* @description update current view data
|
|
* @param {ChartDataType | undefined} data
|
|
*/
|
|
updateCurrentViewData = (data: ChartDataType | undefined) => {
|
|
runInAction(() => {
|
|
this.currentViewData = data;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @description update active block
|
|
* @param {string | null} block
|
|
*/
|
|
updateActiveBlockId = (blockId: string | null) => {
|
|
this.activeBlockId = blockId;
|
|
};
|
|
|
|
/**
|
|
* @description update render view
|
|
* @param {any[]} data
|
|
*/
|
|
updateRenderView = (data: any[]) => {
|
|
this.renderView = data;
|
|
};
|
|
|
|
/**
|
|
* @description initialize gantt chart with month view
|
|
*/
|
|
initGantt = () => {
|
|
const newCurrentViewData = currentViewDataWithView(this.currentView);
|
|
|
|
runInAction(() => {
|
|
this.currentViewData = newCurrentViewData;
|
|
this.blocksMap = {};
|
|
this.blockIds = undefined;
|
|
});
|
|
};
|
|
|
|
/** Gets Block from Id */
|
|
getBlockById = computedFn((blockId: string) => this.blocksMap[blockId]);
|
|
|
|
/**
|
|
* updates the BlocksMap from blockIds
|
|
* @param getDataById
|
|
* @returns
|
|
*/
|
|
updateBlocks(getDataById: (id: string) => BlockData | undefined | null, type?: EGanttBlockType, index?: number) {
|
|
if (!this.blockIds || !Array.isArray(this.blockIds) || this.isDragging) return true;
|
|
|
|
const updatedBlockMaps: { path: string[]; value: any }[] = [];
|
|
const newBlocks: IGanttBlock[] = [];
|
|
|
|
// Loop through blockIds to generate blocks Data
|
|
for (const blockId of this.blockIds) {
|
|
const blockData = getDataById(blockId);
|
|
if (!blockData) continue;
|
|
|
|
const block: IGanttBlock = {
|
|
data: blockData,
|
|
id: blockData?.id,
|
|
name: blockData.name,
|
|
sort_order: blockData?.sort_order ?? undefined,
|
|
start_date: blockData?.start_date ?? undefined,
|
|
target_date: blockData?.target_date ?? undefined,
|
|
meta: {
|
|
type,
|
|
index,
|
|
project_id: blockData?.project_id,
|
|
},
|
|
};
|
|
if (this.currentViewData && (this.currentViewData?.data?.startDate || this.currentViewData?.data?.dayWidth)) {
|
|
block.position = getItemPositionWidth(this.currentViewData, block);
|
|
}
|
|
|
|
// create block updates if the block already exists, or push them to newBlocks
|
|
if (this.blocksMap[blockId]) {
|
|
for (const key of Object.keys(block)) {
|
|
const currValue = this.blocksMap[blockId][key as keyof IGanttBlock];
|
|
const nextValue = block[key as keyof IGanttBlock];
|
|
if (!isEqual(currValue, nextValue)) {
|
|
updatedBlockMaps.push({ path: [blockId, key], value: nextValue });
|
|
}
|
|
}
|
|
} else {
|
|
newBlocks.push(block);
|
|
}
|
|
}
|
|
|
|
// update the store with the block updates
|
|
runInAction(() => {
|
|
for (const updatedBlock of updatedBlockMaps) {
|
|
set(this.blocksMap, updatedBlock.path, updatedBlock.value);
|
|
}
|
|
|
|
for (const newBlock of newBlocks) {
|
|
set(this.blocksMap, [newBlock.id], newBlock);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* returns number of days that the position pixels span across the timeline chart
|
|
* @param position
|
|
* @returns
|
|
*/
|
|
getNumberOfDaysFromPosition = (position: number | undefined) => {
|
|
if (!this.currentViewData || !position) return;
|
|
|
|
return Math.round(position / this.currentViewData.data.dayWidth);
|
|
};
|
|
|
|
/**
|
|
* returns position of the date on chart
|
|
*/
|
|
getPositionFromDateOnGantt = computedFn((date: string | Date, offSetWidth: number) => {
|
|
if (!this.currentViewData) return;
|
|
|
|
return getPositionFromDate(this.currentViewData, date, offSetWidth);
|
|
});
|
|
|
|
/**
|
|
* returns the date at which the position corresponds to on the timeline chart
|
|
*/
|
|
getDateFromPositionOnGantt = computedFn((position: number, offsetDays: number) => {
|
|
if (!this.currentViewData) return;
|
|
|
|
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;
|
|
});
|
|
});
|
|
});
|
|
|
|
/**
|
|
* returns updates dates of blocks post drag.
|
|
* @param id
|
|
* @param shouldUpdateHalfBlock if is a half block then update the incomplete block only if this is true
|
|
* @returns
|
|
*/
|
|
getUpdatedPositionAfterDrag = action((id: string, shouldUpdateHalfBlock: boolean) => {
|
|
const currBlock = this.blocksMap[id];
|
|
|
|
if (!currBlock?.position || !this.currentViewData) return [];
|
|
|
|
const updatePayload: IBlockUpdateDependencyData = { id, meta: currBlock.meta };
|
|
|
|
// If shouldUpdateHalfBlock or the start date is available then update start date
|
|
if (shouldUpdateHalfBlock || currBlock.start_date) {
|
|
updatePayload.start_date = renderFormattedPayloadDate(
|
|
getDateFromPositionOnGantt(currBlock.position.marginLeft, this.currentViewData)
|
|
);
|
|
}
|
|
// If shouldUpdateHalfBlock or the target date is available then update target date
|
|
if (shouldUpdateHalfBlock || currBlock.target_date) {
|
|
updatePayload.target_date = renderFormattedPayloadDate(
|
|
getDateFromPositionOnGantt(currBlock.position.marginLeft + currBlock.position.width, this.currentViewData, -1)
|
|
);
|
|
}
|
|
|
|
return [updatePayload];
|
|
});
|
|
|
|
/**
|
|
* updates the block's position such as marginLeft and width while dragging
|
|
* @param id
|
|
* @param deltaLeft
|
|
* @param deltaWidth
|
|
* @returns
|
|
*/
|
|
updateBlockPosition = action((id: string, deltaLeft: number, deltaWidth: number) => {
|
|
const currBlock = this.blocksMap[id];
|
|
|
|
if (!currBlock?.position) return;
|
|
|
|
const newMarginLeft = currBlock.position.marginLeft + deltaLeft;
|
|
const newWidth = currBlock.position.width + deltaWidth;
|
|
|
|
runInAction(() => {
|
|
set(this.blocksMap, [id, "position"], {
|
|
marginLeft: newMarginLeft ?? currBlock.position?.marginLeft,
|
|
width: newWidth ?? currBlock.position?.width,
|
|
});
|
|
});
|
|
});
|
|
|
|
// Dummy method to return if the current Block's dependency is being dragged
|
|
getIsCurrentDependencyDragging = computedFn((blockId: string) => false);
|
|
}
|