Initial commit: Plane
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled

Synced from upstream: 8853637e981ed7d8a6cff32bd98e7afe20f54362
This commit is contained in:
chuan
2025-11-07 00:00:52 +08:00
commit 8ebde8aa05
4886 changed files with 462270 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import type { ChartDataType, IGanttBlock } from "@plane/types";
import { addDaysToDate, findTotalDaysInRange, getDate } from "@plane/utils";
import { DEFAULT_BLOCK_WIDTH } from "../constants";
/**
* Generates Date by using Day, month and Year
* @param day
* @param month
* @param year
* @returns
*/
export const generateDate = (day: number, month: number, year: number) => new Date(year, month, day);
/**
* Returns number of days in month
* @param month
* @param year
* @returns
*/
export const getNumberOfDaysInMonth = (month: number, year: number) => {
const date = new Date(year, month + 1, 0);
return date.getDate();
};
/**
* Returns week number from date
* @param date
* @returns
*/
export const getWeekNumberByDate = (date: Date) => {
const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
const daysOffset = firstDayOfYear.getDay();
const firstWeekStart = firstDayOfYear.getTime() - daysOffset * 24 * 60 * 60 * 1000;
const weekStart = new Date(firstWeekStart);
const weekNumber = Math.floor((date.getTime() - weekStart.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1;
return weekNumber;
};
/**
* Returns number of days between two dates
* @param startDate
* @param endDate
* @returns
*/
export const getNumberOfDaysBetweenTwoDates = (startDate: Date, endDate: Date) => {
let daysDifference: number = 0;
startDate.setHours(0, 0, 0, 0);
endDate.setHours(0, 0, 0, 0);
const timeDifference: number = startDate.getTime() - endDate.getTime();
daysDifference = Math.round(timeDifference / (1000 * 60 * 60 * 24));
return daysDifference;
};
/**
* returns a date corresponding to the position on the timeline chart
* @param position
* @param chartData
* @param offsetDays
* @returns
*/
export const getDateFromPositionOnGantt = (position: number, chartData: ChartDataType, offsetDays = 0) => {
const numberOfDaysSinceStart = Math.round(position / chartData.data.dayWidth) + offsetDays;
const newDate = addDaysToDate(chartData.data.startDate, numberOfDaysSinceStart);
if (!newDate) undefined;
return newDate;
};
/**
* returns the position and width of the block on the timeline chart from startDate and EndDate
* @param chartData
* @param itemData
* @returns
*/
export const getItemPositionWidth = (chartData: ChartDataType, itemData: IGanttBlock) => {
let scrollPosition: number = 0;
let scrollWidth: number = DEFAULT_BLOCK_WIDTH;
const { startDate: chartStartDate } = chartData.data;
const { start_date, target_date } = itemData;
const itemStartDate = getDate(start_date);
const itemTargetDate = getDate(target_date);
chartStartDate.setHours(0, 0, 0, 0);
itemStartDate?.setHours(0, 0, 0, 0);
itemTargetDate?.setHours(0, 0, 0, 0);
if (!itemStartDate && !itemTargetDate) return;
// get scroll position from the number of days and width of each day
scrollPosition = itemStartDate
? getPositionFromDate(chartData, itemStartDate, 0)
: getPositionFromDate(chartData, itemTargetDate!, -1 * DEFAULT_BLOCK_WIDTH + chartData.data.dayWidth);
if (itemStartDate && itemTargetDate) {
// get width of block
const widthTimeDifference: number = itemStartDate.getTime() - itemTargetDate.getTime();
const widthDaysDifference: number = Math.abs(Math.floor(widthTimeDifference / (1000 * 60 * 60 * 24)));
scrollWidth = (widthDaysDifference + 1) * chartData.data.dayWidth;
}
return { marginLeft: scrollPosition, width: scrollWidth };
};
export const getPositionFromDate = (chartData: ChartDataType, date: string | Date, offsetWidth: number) => {
const currDate = getDate(date);
const { startDate: chartStartDate } = chartData.data;
if (!currDate || !chartStartDate) return 0;
chartStartDate.setHours(0, 0, 0, 0);
currDate.setHours(0, 0, 0, 0);
// get number of days from chart start date to block's start date
const positionDaysDifference = Math.round(findTotalDaysInRange(chartStartDate, currDate, false) ?? 0);
if (!positionDaysDifference) return 0;
// get scroll position from the number of days and width of each day
return positionDaysDifference * chartData.data.dayWidth + offsetWidth;
};

View File

@@ -0,0 +1,4 @@
export * from "./week-view";
export * from "./month-view";
export * from "./quarter-view";
export * from "./helpers";

View File

@@ -0,0 +1,171 @@
import { cloneDeep, uniqBy } from "lodash-es";
// plane imports
import type { ChartDataType } from "@plane/types";
// local imports
import { months } from "../data";
import { getNumberOfDaysBetweenTwoDates, getNumberOfDaysInMonth } from "./helpers";
import type { IWeekBlock } from "./week-view";
import { getWeeksBetweenTwoDates } from "./week-view";
export interface IMonthBlock {
today: boolean;
month: number;
days: number;
monthData: {
key: number;
shortTitle: string;
title: string;
};
title: string;
year: number;
}
export interface IMonthView {
months: IMonthBlock[];
weeks: IWeekBlock[];
}
/**
* Generate Month Chart data
* @param monthPayload
* @param side
* @returns
*/
const generateMonthChart = (monthPayload: ChartDataType, side: null | "left" | "right", targetDate?: Date) => {
let renderState = cloneDeep(monthPayload);
const range: number = renderState.data.approxFilterRange || 6;
let filteredDates: IMonthView = { months: [], weeks: [] };
let minusDate: Date = new Date();
let plusDate: Date = new Date();
let startDate = new Date();
let endDate = new Date();
// if side is null generate months on both side of current date
if (side === null) {
const currentDate = renderState.data.currentDate;
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate);
startDate = filteredDates.weeks[0]?.startDate;
endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate;
renderState = {
...renderState,
data: {
...renderState.data,
startDate,
endDate,
},
};
}
// When side is left, generate more months on the left side of the start date
else if (side === "left") {
const chartStartDate = renderState.data.startDate;
const currentDate = targetDate ? targetDate : chartStartDate;
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1);
plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1);
if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate);
startDate = filteredDates.weeks[0]?.startDate;
endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1);
renderState = {
...renderState,
data: { ...renderState.data, startDate },
};
}
// When side is right, generate more months on the right side of the end date
else if (side === "right") {
const chartEndDate = renderState.data.endDate;
const currentDate = targetDate ? targetDate : chartEndDate;
minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1);
if (minusDate && plusDate) filteredDates = getMonthsViewBetweenTwoDates(minusDate, plusDate);
startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1);
endDate = filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate;
renderState = {
...renderState,
data: { ...renderState.data, endDate: filteredDates.weeks[filteredDates.weeks.length - 1]?.endDate },
};
}
const days = Math.abs(getNumberOfDaysBetweenTwoDates(startDate, endDate)) + 1;
const scrollWidth = days * monthPayload.data.dayWidth;
return { state: renderState, payload: filteredDates, scrollWidth: scrollWidth };
};
/**
* Get Month View data between two dates, i.e., Months and Weeks between two dates
* @param startDate
* @param endDate
* @returns
*/
const getMonthsViewBetweenTwoDates = (startDate: Date, endDate: Date): IMonthView => ({
months: getMonthsBetweenTwoDates(startDate, endDate),
weeks: getWeeksBetweenTwoDates(startDate, endDate, false),
});
/**
* generate array of months between two dates
* @param startDate
* @param endDate
* @returns
*/
export const getMonthsBetweenTwoDates = (startDate: Date, endDate: Date): IMonthBlock[] => {
const monthBlocks = [];
const startYear = startDate.getFullYear();
const startMonth = startDate.getMonth();
const today = new Date();
const todayMonth = today.getMonth();
const todayYear = today.getFullYear();
const currentDate = new Date(startYear, startMonth);
while (currentDate <= endDate) {
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth();
monthBlocks.push({
year: currentYear,
month: currentMonth,
monthData: months[currentMonth],
title: `${months[currentMonth].title} ${currentYear}`,
days: getNumberOfDaysInMonth(currentMonth, currentYear),
today: todayMonth === currentMonth && todayYear === currentYear,
});
currentDate.setMonth(currentDate.getMonth() + 1);
}
return monthBlocks;
};
/**
* Merge two MonthView data payloads
* @param a
* @param b
* @returns
*/
const mergeMonthRenderPayloads = (a: IMonthView, b: IMonthView): IMonthView => ({
months: uniqBy([...a.months, ...b.months], (monthBlock) => `${monthBlock.month}_${monthBlock.year}`),
weeks: uniqBy(
[...a.weeks, ...b.weeks],
(weekBlock) => `${weekBlock.startDate.getTime()}_${weekBlock.endDate.getTime()}`
),
});
export const monthView = {
generateChart: generateMonthChart,
mergeRenderPayloads: mergeMonthRenderPayloads,
};

View File

@@ -0,0 +1,148 @@
//
import type { ChartDataType } from "@plane/types";
import { quarters } from "../data";
import { getNumberOfDaysBetweenTwoDates } from "./helpers";
import type { IMonthBlock } from "./month-view";
import { getMonthsBetweenTwoDates } from "./month-view";
export interface IQuarterMonthBlock {
children: IMonthBlock[];
quarterNumber: number;
shortTitle: string;
title: string;
year: number;
today: boolean;
}
/**
* Generate Quarter Chart data, which in turn are months in an array
* @param quarterPayload
* @param side
* @returns
*/
const generateQuarterChart = (quarterPayload: ChartDataType, side: null | "left" | "right", targetDate?: Date) => {
let renderState = quarterPayload;
const range: number = renderState.data.approxFilterRange || 12;
let filteredDates: IMonthBlock[] = [];
let minusDate: Date = new Date();
let plusDate: Date = new Date();
let startDate = new Date();
let endDate = new Date();
// if side is null generate months on both side of current date
if (side === null) {
const currentDate = renderState.data.currentDate;
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 0);
if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
const startMonthBlock = filteredDates[0];
const endMonthBlock = filteredDates[filteredDates.length - 1];
startDate = new Date(startMonthBlock.year, startMonthBlock.month, 1);
endDate = new Date(endMonthBlock.year, endMonthBlock.month + 1, 0);
renderState = {
...renderState,
data: {
...renderState.data,
startDate,
endDate,
},
};
}
// When side is left, generate more months on the left side of the start date
else if (side === "left") {
const chartStartDate = renderState.data.startDate;
const currentDate = targetDate ? targetDate : chartStartDate;
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range / 2, 1);
plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth() - 1, 1);
if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
const startMonthBlock = filteredDates[0];
startDate = new Date(startMonthBlock.year, startMonthBlock.month, 1);
endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1);
renderState = {
...renderState,
data: { ...renderState.data, startDate },
};
}
// When side is right, generate more months on the right side of the end date
else if (side === "right") {
const chartEndDate = renderState.data.endDate;
const currentDate = targetDate ? targetDate : chartEndDate;
minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth() + 1, 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range / 2, 1);
if (minusDate && plusDate) filteredDates = getMonthsBetweenTwoDates(minusDate, plusDate);
const endMonthBlock = filteredDates[filteredDates.length - 1];
startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1);
endDate = new Date(endMonthBlock.year, endMonthBlock.month + 1, 0);
renderState = {
...renderState,
data: { ...renderState.data, endDate },
};
}
const days = Math.abs(getNumberOfDaysBetweenTwoDates(startDate, endDate)) + 1;
const scrollWidth = days * quarterPayload.data.dayWidth;
return { state: renderState, payload: filteredDates, scrollWidth: scrollWidth };
};
/**
* Merge two Quarter data payloads
* @param a
* @param b
* @returns
*/
const mergeQuarterRenderPayloads = (a: IMonthBlock[], b: IMonthBlock[]) => [...a, ...b];
/**
* Group array of Months into Quarters, returns an array og Quarters and it's children Months
* @param monthBlocks
* @returns
*/
export const groupMonthsToQuarters = (monthBlocks: IMonthBlock[]): IQuarterMonthBlock[] => {
const quartersMap: { [key: string]: IQuarterMonthBlock } = {};
const today = new Date();
const todayQuarterNumber = Math.floor(today.getMonth() / 3);
const todayYear = today.getFullYear();
for (const monthBlock of monthBlocks) {
const { month, year } = monthBlock;
const quarterNumber = Math.floor(month / 3);
const quarterKey = `Q${quarterNumber}-${year}`;
if (quartersMap[quarterKey]) {
quartersMap[quarterKey].children.push(monthBlock);
} else {
const quarterData = quarters[quarterNumber];
quartersMap[quarterKey] = {
children: [monthBlock],
quarterNumber,
shortTitle: quarterData.shortTitle,
title: `${quarterData.title} ${year}`,
year,
today: todayQuarterNumber === quarterNumber && todayYear === year,
};
}
}
return Object.values(quartersMap);
};
export const quarterView = {
generateChart: generateQuarterChart,
mergeRenderPayloads: mergeQuarterRenderPayloads,
};

View File

@@ -0,0 +1,214 @@
//
import type { ChartDataType } from "@plane/types";
import { EStartOfTheWeek } from "@plane/types";
import { months, generateWeeks } from "../data";
import { getNumberOfDaysBetweenTwoDates, getWeekNumberByDate } from "./helpers";
export interface IDayBlock {
date: Date;
day: number;
dayData: {
key: number;
shortTitle: string;
title: string;
abbreviation: string;
};
title: string;
today: boolean;
}
export interface IWeekBlock {
children?: IDayBlock[];
weekNumber: number;
weekData: {
shortTitle: string;
title: string;
};
title: string;
startDate: Date;
endDate: Date;
startMonth: number;
startYear: number;
endMonth: number;
endYear: number;
today: boolean;
}
/**
* Generate Week Chart data
* @param weekPayload
* @param side
* @returns
*/
const generateWeekChart = (
weekPayload: ChartDataType,
side: null | "left" | "right",
targetDate?: Date,
startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY
) => {
let renderState = weekPayload;
const range: number = renderState.data.approxFilterRange || 6;
let filteredDates: IWeekBlock[] = [];
let minusDate: Date = new Date();
let plusDate: Date = new Date();
let startDate = new Date();
let endDate = new Date();
// if side is null generate weeks on both side of current date
if (side === null) {
const currentDate = renderState.data.currentDate;
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, currentDate.getDate());
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, currentDate.getDate());
if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate, true, startOfWeek);
startDate = filteredDates[0].startDate;
endDate = filteredDates[filteredDates.length - 1].endDate;
renderState = {
...renderState,
data: {
...renderState.data,
startDate,
endDate,
},
};
}
// When side is left, generate more weeks on the left side of the start date
else if (side === "left") {
const chartStartDate = renderState.data.startDate;
const currentDate = targetDate ? targetDate : chartStartDate;
minusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - range, 1);
plusDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1);
if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate, true, startOfWeek);
startDate = filteredDates[0].startDate;
endDate = new Date(chartStartDate.getFullYear(), chartStartDate.getMonth(), chartStartDate.getDate() - 1);
renderState = {
...renderState,
data: { ...renderState.data, startDate },
};
}
// When side is right, generate more weeks on the right side of the end date
else if (side === "right") {
const chartEndDate = renderState.data.endDate;
const currentDate = targetDate ? targetDate : chartEndDate;
minusDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1);
plusDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + range, 1);
if (minusDate && plusDate) filteredDates = getWeeksBetweenTwoDates(minusDate, plusDate, true, startOfWeek);
startDate = new Date(chartEndDate.getFullYear(), chartEndDate.getMonth(), chartEndDate.getDate() + 1);
endDate = filteredDates[filteredDates.length - 1].endDate;
renderState = {
...renderState,
data: { ...renderState.data, endDate },
};
}
const days = Math.abs(getNumberOfDaysBetweenTwoDates(startDate, endDate)) + 1;
const scrollWidth = days * weekPayload.data.dayWidth;
return { state: renderState, payload: filteredDates, scrollWidth: scrollWidth };
};
/**
* Generate weeks array between two dates
* @param startDate
* @param endDate
* @param shouldPopulateDaysForWeek
* @returns
*/
export const getWeeksBetweenTwoDates = (
startDate: Date,
endDate: Date,
shouldPopulateDaysForWeek: boolean = true,
startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY
): IWeekBlock[] => {
const weeks: IWeekBlock[] = [];
const currentDate = new Date(startDate.getTime());
const today = new Date();
// Adjust the current date to the start of the week
const day = currentDate.getDay();
const diff = (day + 7 - startOfWeek) % 7; // Calculate days to subtract to get to startOfWeek
currentDate.setDate(currentDate.getDate() - diff);
while (currentDate <= endDate) {
const weekStartDate = new Date(currentDate.getTime());
const weekEndDate = new Date(currentDate.getTime() + 6 * 24 * 60 * 60 * 1000);
const monthAtStartOfTheWeek = weekStartDate.getMonth();
const yearAtStartOfTheWeek = weekStartDate.getFullYear();
const monthAtEndOfTheWeek = weekEndDate.getMonth();
const yearAtEndOfTheWeek = weekEndDate.getFullYear();
const weekNumber = getWeekNumberByDate(currentDate);
weeks.push({
children: shouldPopulateDaysForWeek ? populateDaysForWeek(weekStartDate, startOfWeek) : undefined,
weekNumber,
weekData: {
shortTitle: `w${weekNumber}`,
title: `Week ${weekNumber}`,
},
title:
monthAtStartOfTheWeek === monthAtEndOfTheWeek
? `${months[monthAtStartOfTheWeek].abbreviation} ${yearAtStartOfTheWeek}`
: `${months[monthAtStartOfTheWeek].abbreviation} ${yearAtStartOfTheWeek} - ${months[monthAtEndOfTheWeek].abbreviation} ${yearAtEndOfTheWeek}`,
startMonth: monthAtStartOfTheWeek,
startYear: yearAtStartOfTheWeek,
endMonth: monthAtEndOfTheWeek,
endYear: yearAtEndOfTheWeek,
startDate: weekStartDate,
endDate: weekEndDate,
today: today >= weekStartDate && today <= weekEndDate ? true : false,
});
currentDate.setDate(currentDate.getDate() + 7);
}
return weeks;
};
/**
* return back array of 7 days from the date provided
* @param startDate
* @returns
*/
const populateDaysForWeek = (startDate: Date, startOfWeek: EStartOfTheWeek = EStartOfTheWeek.SUNDAY): IDayBlock[] => {
const currentDate = new Date(startDate);
const days: IDayBlock[] = [];
const today = new Date();
const weekDays = generateWeeks(startOfWeek);
for (let i = 0; i < 7; i++) {
days.push({
date: new Date(currentDate),
day: currentDate.getDay(),
dayData: weekDays[i],
title: `${weekDays[i].abbreviation} ${currentDate.getDate()}`,
today: today.setHours(0, 0, 0, 0) == currentDate.setHours(0, 0, 0, 0),
});
currentDate.setDate(currentDate.getDate() + 1);
}
return days;
};
/**
* Merge two Week data payloads
* @param a
* @param b
* @returns
*/
const mergeWeekRenderPayloads = (a: IWeekBlock[], b: IWeekBlock[]) => [...a, ...b];
export const weekView = {
generateChart: generateWeekChart,
mergeRenderPayloads: mergeWeekRenderPayloads,
};