lodash#min TypeScript Examples

The following examples show how to use lodash#min. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: analysis-utils.ts    From prism-frontend with MIT License 7 votes vote down vote up
/**
 * Creates Analysis result legend based on data returned from API.
 *
 * The equal interval method takes the maximum values minus the minimum
 * and divides the result by the number of classes, which is the length
 * of colors array.
 *
 * Finally the function calculates the upper end of each class interval
 * and assigns a color.
 *
 * @return LegendDefinition
 */
export function createLegendFromFeatureArray(
  features: Feature[],
  statistic: AggregationOperations,
): LegendDefinition {
  // Extract values based on aggregation operation.
  const stats: number[] = features.map(f =>
    f.properties && f.properties[statistic] ? f.properties[statistic] : 0,
  );

  const maxNum = Math.max(...stats);
  const minNum = Math.min(...stats);

  const colors = ['#fee5d9', '#fcae91', '#fb6a4a', '#de2d26', '#a50f15'];

  const delta = (maxNum - minNum) / colors.length;

  const legend: LegendDefinition = colors.map((color, index) => {
    const breakpoint = Math.ceil(minNum + (index + 1) * delta);

    // Make sure you don't have a value greater than maxNum.
    const value = Math.min(breakpoint, maxNum);

    return { value, color };
  });

  return legend;
}
Example #2
Source File: analysis-utils.ts    From prism-frontend with MIT License 7 votes vote down vote up
operations = {
  min: (data: number[]) => min(data),
  max: (data: number[]) => max(data),
  sum, // sum method directly from lodash
  mean, // mean method directly from lodash
  median: (data: number[]) => {
    // eslint-disable-next-line fp/no-mutating-methods
    const sortedValues = [...data].sort();
    // Odd cases we use the middle value
    if (sortedValues.length % 2 !== 0) {
      return sortedValues[Math.floor(sortedValues.length / 2)];
    }
    // Even cases we average the two middles
    const floor = sortedValues.length / 2 - 1;
    const ceil = sortedValues.length / 2;
    return (sortedValues[floor] + sortedValues[ceil]) / 2;
  },
}
Example #3
Source File: utils.ts    From gant-design with MIT License 7 votes vote down vote up
export function getAllChildrenNode(
  targetKeys: any[],
  api: GridApi,
  deleteChildren = false,
): RowNode[] {
  const targetNodes: RowNode[] = [];
  targetKeys.map(key => {
    const itemNode = api.getRowNode(key);
    itemNode && targetNodes.push(itemNode);
  });
  if (deleteChildren) return targetNodes;
  const allNodes: RowNode[] = [];
  const groupNodes = groupBy(targetNodes, 'level');
  let level = min(Object.keys(groupNodes).map(level => level));
  while (!isEmpty(groupNodes[level])) {
    const list = groupNodes[level];
    let nextLevel: any = parseInt(level) + 1;
    nextLevel = nextLevel.toString();
    groupNodes[nextLevel] = groupNodes[nextLevel] ? groupNodes[nextLevel] : [];
    list.map(itemNode => {
      const { childrenAfterGroup = [] } = itemNode as RowNode;
      groupNodes[nextLevel].push(...childrenAfterGroup);
      return itemNode.data;
    });
    groupNodes[nextLevel] = uniqBy(groupNodes[nextLevel], 'id');
    allNodes.push(...list);
    level = nextLevel;
  }
  return allNodes;
}
Example #4
Source File: base-data-set.ts    From S2 with MIT License 6 votes vote down vote up
public getValueRangeByField(field: string): ValueRange {
    const cacheRange = getValueRangeState(this.spreadsheet, field);
    if (cacheRange) {
      return cacheRange;
    }
    const fieldValues = compact(
      map(this.originData, (item) => {
        const value = item[field] as string;
        return isNil(value) ? null : Number.parseFloat(value);
      }),
    );
    const range = {
      maxValue: max(fieldValues),
      minValue: min(fieldValues),
    };
    setValueRangeState(this.spreadsheet, {
      [field]: range,
    });
    return range;
  }
Example #5
Source File: github_analyzer.ts    From CIAnalyzer with MIT License 5 votes vote down vote up
createWorkflowReport(workflowName: string, workflow: WorkflowRunsItem, jobs: JobsItem, tagMap: RepositoryTagMap): WorkflowReport {
    const { workflowId, buildNumber, workflowRunId }
      = this.createWorkflowParams(workflowName, workflow)

    const jobReports: JobReport[] = jobs.map((job) => {
      const stepReports: StepReport[] = job.steps!.map((step) => {
        const startedAt = new Date(step.started_at!)
        const completedAt = new Date(step.completed_at!)
        // step
        return {
          name: step.name,
          status: this.normalizeStatus(step.conclusion),
          number: step.number,
          startedAt,
          completedAt,
          stepDurationSec: diffSec(startedAt, completedAt)
        }
      })

      const startedAt = new Date(job.started_at)
      const completedAt = new Date(job.completed_at!)
      // job
      return {
        workflowRunId: workflowRunId,
        buildNumber: buildNumber, // Github Actions job does not have buildNumber
        jobId: String(job.id),
        jobName: job.name,
        status: this.normalizeStatus(job.conclusion),
        startedAt,
        completedAt,
        jobDurationSec: diffSec(startedAt, completedAt),
        sumStepsDurationSec: sumBy(stepReports, 'stepDurationSec'),
        steps: stepReports,
        url: job.html_url ?? '',
        executorClass: '',
        executorType: '',
        executorName: job.runner_name ?? '',
      }
    })

    const createdAt = new Date(workflow.created_at)
    const startedAt = min(jobReports.map((job) => job.startedAt )) || createdAt
    const completedAt = max(jobReports.map((job) => job.completedAt )) || createdAt
    const status = this.normalizeStatus(workflow.conclusion as unknown as string)
    // workflow
    return {
      service: 'github',
      workflowId,
      buildNumber,
      workflowRunId,
      workflowName,
      createdAt,
      trigger: workflow.event,
      status,
      repository: workflow.repository.full_name,
      headSha: workflow.head_sha,
      branch: workflow.head_branch ?? '',
      tag: tagMap.get(workflow.head_sha) ?? '',
      jobs: jobReports,
      startedAt,
      completedAt,
      workflowDurationSec: diffSec(startedAt, completedAt),
      sumJobsDurationSec: sumBy(jobReports, 'sumStepsDurationSec'),
      successCount: (status === 'SUCCESS') ? 1 : 0,
      parameters: [],
      queuedDurationSec: diffSec(createdAt, startedAt),
      commitMessage: workflow.head_commit?.message ?? '',
      actor: workflow.head_commit?.author?.name ?? '',
      url: workflow.html_url,
    }
  }
Example #6
Source File: date-helper.ts    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
  let newStartDate: Date = tasks[0].start || 0;
  let newEndDate: Date = tasks[0].start || 0;

  const timeArr = compact(flatten(tasks.map((item) => [item.start, item.end])));

  const minTime = min(timeArr);
  const maxTime = max(timeArr);
  newStartDate = minTime && new Date(minTime);
  newEndDate = maxTime && new Date(maxTime);

  if (!newStartDate) {
    newStartDate = new Date(moment().subtract(15, 'days'));
  }
  if (!newEndDate || newEndDate.getTime() === newStartDate.getTime()) {
    newEndDate = new Date(moment(newStartDate).subtract(-30, 'days'));
  }

  // start time is bigger then end time
  if (newStartDate.getTime() > newEndDate.getTime()) {
    [newStartDate, newEndDate] = [newEndDate, newStartDate];
  }

  switch (viewMode) {
    case ViewMode.Month:
      newStartDate = addToDate(newStartDate, -1, 'month');
      newStartDate = startOfDate(newStartDate, 'month');
      newEndDate = addToDate(newEndDate, 1, 'year');
      newEndDate = startOfDate(newEndDate, 'year');
      break;
    case ViewMode.Week:
      newStartDate = startOfDate(newStartDate, 'day');
      newEndDate = startOfDate(newEndDate, 'day');
      newStartDate = addToDate(getMonday(newStartDate), -7, 'day');
      newEndDate = addToDate(newEndDate, 1.5, 'month');
      break;
    case ViewMode.Day:
      newStartDate = startOfDate(newStartDate, 'day');
      newEndDate = startOfDate(newEndDate, 'day');
      newStartDate = addToDate(newStartDate, -1, 'day');
      newEndDate = addToDate(newEndDate, 19, 'day');
      break;
    case ViewMode.QuarterDay:
      newStartDate = startOfDate(newStartDate, 'day');
      newEndDate = startOfDate(newEndDate, 'day');
      newStartDate = addToDate(newStartDate, -1, 'day');
      newEndDate = addToDate(newEndDate, 66, 'hour'); // 24(1 day)*3 - 6
      break;
    case ViewMode.HalfDay:
      newStartDate = startOfDate(newStartDate, 'day');
      newEndDate = startOfDate(newEndDate, 'day');
      newStartDate = addToDate(newStartDate, -1, 'day');
      newEndDate = addToDate(newEndDate, 108, 'hour'); // 24(1 day)*5 - 12
      break;
    default:
      break;
  }
  return [newStartDate, newEndDate];
}
Example #7
Source File: circleci_analyzer.ts    From CIAnalyzer with MIT License 4 votes vote down vote up
createWorkflowReport( workflowRun: WorkflowRun, jobs: SingleBuildResponse[], tagMap: RepositoryTagMap): WorkflowReport {
    const sortedJobs = sortBy(jobs, 'build_num')
    const firstJob = first(sortedJobs)!
    const lastJob = last(sortedJobs)!
    const repository = `${firstJob.username}/${firstJob.reponame}`
    const { workflowName, workflowId, buildNumber, workflowRunId }
      = this.createWorkflowParams(workflowRun.workflow_name, repository, lastJob.build_num)

    const jobReports: JobReport[] = sortedJobs.map((job) => {
      const stepReports: StepReport[] = job.steps
        .filter((step) => {
          const action = first(step.actions)!
          // NOTE: Ignore background step (ex. Setup service container image step)
          return action.background === false
        })
        .map((step) => {
          const action = first(step.actions)!
          const startedAt = new Date(action.start_time)
          // NOTE: Sometimes action.end_time will be broken, so it should be replaced when it's value is invalid.
          const validatedEndTime = action.end_time ?? action.start_time
          const completedAt = new Date(validatedEndTime)
          // step
          return {
            name: action.name,
            status: this.normalizeStatus(action.status),
            number: action.step,
            startedAt,
            completedAt,
            stepDurationSec: diffSec(startedAt, completedAt)
          }
        })

      const startedAt = new Date(job.start_time)
      const completedAt = new Date(job.stop_time)
      // job
      return {
        workflowRunId,
        buildNumber: job.build_num,
        jobId: job.workflows.job_id,
        jobName: job.workflows.job_name,
        status: this.normalizeStatus(job.status),
        startedAt,
        completedAt,
        jobDurationSec: diffSec(startedAt, completedAt),
        sumStepsDurationSec: secRound(sumBy(stepReports, 'stepDurationSec')),
        steps: stepReports,
        url: '',
        executorClass: '',
        executorType: '',
        executorName: '',
      }
    })

    const startedAt = min(jobReports.map((job) => job.startedAt ))!
    const completedAt = max(jobReports.map((job) => job.completedAt ))!
    const status = this.estimateWorkflowStatus(jobReports)
    const createdAt = min(jobs.map((job) => new Date(job.queued_at)))!
    // workflow
    return {
      service: 'circleci',
      workflowId,
      buildNumber,
      workflowRunId,
      workflowName,
      createdAt,
      trigger: firstJob.why,
      status,
      repository,
      headSha: firstJob.vcs_revision,
      branch: firstJob.branch,
      tag: tagMap.get(firstJob.vcs_revision) ?? '',
      jobs: jobReports,
      startedAt,
      completedAt,
      workflowDurationSec: diffSec(startedAt, completedAt),
      sumJobsDurationSec: secRound(sumBy(jobReports, 'sumStepsDurationSec')),
      successCount: (status === 'SUCCESS') ? 1 : 0,
      parameters: [],
      queuedDurationSec: diffSec(createdAt, min(jobs.map((job) => new Date(job.start_time)))!),
      commitMessage: '',
      actor: '',
      url: '',
    }
  }
Example #8
Source File: philosophers.tsx    From S2 with MIT License 4 votes vote down vote up
fetch(
  'https://gw.alipayobjects.com/os/bmw-prod/24cac0f7-70f0-4131-be61-df11da3ca921.json',
)
  .then((res) => res.json())
  .then((data) => {
    const weights = data.map((item) => item.weight);
    const maxWeight = max(weights);
    const minWeight = min(weights);
    const weightSpan = maxWeight - minWeight;

    const PaletteLegend = () => (
      <div className="legend">
        <div className="legend-limit">{minWeight.toFixed(2)}</div>
        {PALETTE_COLORS.map((color, index) => (
          <span
            key={index}
            className="legend-color"
            style={{ background: color }}
          />
        ))}
        <div className="legend-limit">{maxWeight.toFixed(2)}</div>
      </div>
    );

    const getFormatter = (val) => {
      if (val < 0) {
        return `公元前${replace(val, '-', '')}年`;
      } else {
        return `${val}年`;
      }
    };

    const s2DataConfig = {
      fields: {
        rows: ['country', 'name', 'start', 'end', 'points', 'word'],
        columns: [],
        values: ['weight'],
      },
      meta: [
        {
          field: 'word',
          name: '关键词',
        },
        {
          field: 'points',
          name: '观点',
        },
        {
          field: 'name',
          name: '姓名',
        },
        {
          field: 'country',
          name: '国家',
        },
        {
          field: 'start',
          name: '出生',
          formatter: getFormatter,
        },
        {
          field: 'end',
          name: '逝世',
          formatter: getFormatter,
        },
        {
          field: 'weight',
          name: '权重',
          formatter: (val) => val.toFixed(2),
        },
      ],
      data,
    };
    const TooltipContent = (props) => {
      const { rowQuery, fieldValue } = props;
      const { name, country, start, end, points } = rowQuery;
      const ponitsLines = points.split('&');
      return (
        <div className="antv-s2-tooltip-container">
          <div className="antv-s2-tooltip-head-info-list">
            <div>姓名:{name}</div>
            <div>国家:{country}</div>
            <div>出生:{getFormatter(start)}</div>
            <div>逝世:{getFormatter(end)}</div>
            {ponitsLines.length > 1 ? (
              <div>
                观点:
                {ponitsLines.map((point, index) => (
                  <div>
                    {index + 1}: {point}
                  </div>
                ))}
              </div>
            ) : (
              <div>观点: {ponitsLines[0]}</div>
            )}
          </div>
          <div className="antv-s2-tooltip-divider"></div>
          <div className="antv-s2-tooltip-detail-list">
            <div className="antv-s2-tooltip-detail-item">
              <span className="antv-s2-tooltip-detail-item-key">权重</span>
              <span className="antv-s2-tooltip-detail-item-val">
                {fieldValue}
              </span>
            </div>
          </div>
        </div>
      );
    };
    const s2Options = {
      width: '',
      height: 400,
      conditions: {
        text: [
          {
            field: 'weight',
            mapping(value) {
              if (value >= 20) {
                return {
                  fill: '#fff',
                };
              }
            },
          },
        ],
        background: [
          {
            field: 'weight',
            mapping(value) {
              let backgroundColor;
              const colorIndex =
                Math.floor(
                  (((value - minWeight) / weightSpan) * 100) /
                    PALETTE_COLORS.length,
                ) - 1;
              if (colorIndex <= 0) {
                backgroundColor = PALETTE_COLORS[0];
              } else if (colorIndex >= PALETTE_COLORS.length) {
                backgroundColor = PALETTE_COLORS[PALETTE_COLORS.length - 1];
              } else {
                backgroundColor = PALETTE_COLORS[colorIndex];
              }

              return {
                fill: backgroundColor,
              };
            },
          },
        ],
      },
      interaction: {
        selectedCellsSpotlight: false,
        hoverHighlight: false,
      },
    };

    const onDataCellMouseUp = (value) => {
      const viewMeta = value?.viewMeta;
      if (!viewMeta) {
        return;
      }

      const position = {
        x: value.event.clientX,
        y: value.event.clientY,
      };
      viewMeta.spreadsheet.tooltip.show({
        position,
        content: TooltipContent(viewMeta),
      });
    };

    ReactDOM.render(
      <SheetComponent
        dataCfg={s2DataConfig}
        options={s2Options}
        adaptive={true}
        header={{
          title: '哲学家的观点',
          extra: [<PaletteLegend />],
        }}
        onDataCellMouseUp={onDataCellMouseUp}
      />,

      document.getElementById('container'),
    );
  });
Example #9
Source File: time-series.spec.ts    From aqualink-app with MIT License 4 votes vote down vote up
timeSeriesTests = () => {
  const testService = TestService.getInstance();
  let app: INestApplication;
  let surveyPointDataRange: StringDateRange = [
    new Date(0).toISOString(),
    new Date().toISOString(),
  ];
  let siteDataRange: StringDateRange = [
    new Date(0).toISOString(),
    new Date().toISOString(),
  ];

  beforeAll(async () => {
    app = await testService.getApp();
  });

  it('GET /sites/:siteId/site-survey-points/:surveyPointId/range fetch range of poi data', async () => {
    const rsp = await request(app.getHttpServer()).get(
      `/time-series/sites/${athensSite.id}/site-survey-points/${athensSurveyPointPiraeus.id}/range`,
    );

    expect(rsp.status).toBe(200);
    const metrics = union(hoboMetrics, NOAAMetrics);
    metrics.forEach((metric) => {
      expect(rsp.body).toHaveProperty(metric);
    });
    hoboMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.HOBO);
      expect(rsp.body[metric][SourceType.HOBO].data.length).toBe(1);
      const { minDate, maxDate } = rsp.body[metric][SourceType.HOBO].data[0];
      const [startDate, endDate] = surveyPointDataRange;
      surveyPointDataRange = [
        min([minDate, startDate]),
        max([maxDate, endDate]),
      ];
    });
    NOAAMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.NOAA);
      expect(rsp.body[metric][SourceType.NOAA].data.length).toBe(1);
      const { minDate, maxDate } = rsp.body[metric][SourceType.NOAA].data[0];
      const [startDate, endDate] = surveyPointDataRange;
      surveyPointDataRange = [
        min([minDate, startDate]),
        max([maxDate, endDate]),
      ];
    });
  });

  it('GET /sites/:id/range fetch range of site data', async () => {
    const rsp = await request(app.getHttpServer()).get(
      `/time-series/sites/${californiaSite.id}/range`,
    );

    expect(rsp.status).toBe(200);
    const metrics = union(NOAAMetrics, spotterMetrics);
    metrics.forEach((metric) => {
      expect(rsp.body).toHaveProperty(metric);
    });
    NOAAMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.NOAA);
      expect(rsp.body[metric][SourceType.NOAA].data.length).toBe(1);
      const { minDate, maxDate } = rsp.body[metric][SourceType.NOAA].data[0];
      const [startDate, endDate] = siteDataRange;
      siteDataRange = [min([minDate, startDate]), max([maxDate, endDate])];
    });
    spotterMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.SPOTTER);
      expect(rsp.body[metric][SourceType.SPOTTER].data.length).toBe(1);
      const { minDate, maxDate } = rsp.body[metric][SourceType.SPOTTER].data[0];
      const [startDate, endDate] = siteDataRange;
      siteDataRange = [min([minDate, startDate]), max([maxDate, endDate])];
    });
  });

  it('GET /sites/:siteId/site-survey-points/:surveyPointId fetch poi data', async () => {
    const [startDate, endDate] = surveyPointDataRange;
    const rsp = await request(app.getHttpServer())
      .get(
        `/time-series/sites/${athensSite.id}/site-survey-points/${athensSurveyPointPiraeus.id}`,
      )
      .query({
        // Increase the search window to combat precision issues with the dates
        start: moment(startDate).subtract(1, 'minute').toISOString(),
        end: moment(endDate).add(1, 'day').toISOString(),
        metrics: hoboMetrics.concat(NOAAMetrics),
        hourly: false,
      });

    expect(rsp.status).toBe(200);
    const metrics = union(hoboMetrics, NOAAMetrics);
    metrics.forEach((metric) => {
      expect(rsp.body).toHaveProperty(metric);
    });
    hoboMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.HOBO);
      expect(rsp.body[metric][SourceType.HOBO].data.length).toBe(10);
    });
    NOAAMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.NOAA);
      expect(rsp.body[metric][SourceType.NOAA].data.length).toBe(10);
    });
  });

  it('GET /sites/:siteId fetch site data', async () => {
    const [startDate, endDate] = siteDataRange;
    const rsp = await request(app.getHttpServer())
      .get(`/time-series/sites/${californiaSite.id}`)
      .query({
        // Increase the search window to combat precision issues with the dates
        start: moment(startDate).subtract(1, 'minute').toISOString(),
        end: moment(endDate).add(1, 'day').toISOString(),
        metrics: spotterMetrics.concat(NOAAMetrics),
        hourly: false,
      });

    expect(rsp.status).toBe(200);
    const metrics = union(NOAAMetrics, spotterMetrics);
    metrics.forEach((metric) => {
      expect(rsp.body).toHaveProperty(metric);
    });
    NOAAMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.NOAA);
      expect(rsp.body[metric][SourceType.NOAA].data.length).toBe(10);
    });
    spotterMetrics.forEach((metric) => {
      expect(rsp.body[metric]).toHaveProperty(SourceType.SPOTTER);
      expect(rsp.body[metric][SourceType.SPOTTER].data.length).toBe(10);
    });
  });
}
Example #10
Source File: TaskCalendar.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function TaskCalendar(props: TaskCalendarProps): React.ReactElement {
  const {
    briefList,
    taskList,
    importantList,
    importanceSettings,
    taskSettings,
    onDateSelect,
    onPickerPanelChange,
    value,
    defaultSelectedDate,
    footerStyle,
    dateCellHeight,
    showLunarInfo,
    mode,
  } = props;
  const [selectedData, setSelectedData] = useState<{
    date: Moment;
    data: DateDetail["data"];
  }>({} as any);

  const briefDataMap = useMemo(() => {
    return briefList?.reduce((pre, cur) => {
      const curMoment = moment(cur.date).format("YYYY-MM-DD");
      pre[curMoment] = cur.text;
      return pre;
    }, {} as Record<BriefData["date"], BriefData["text"]>);
  }, [briefList]);

  const taskDataMap = useMemo(() => {
    return taskList?.reduce((pre, cur) => {
      const curMoment = moment(cur.date).format("YYYY-MM-DD");
      pre[curMoment] = cur.task;
      return pre;
    }, {} as Record<TaskData["date"], TaskData["task"]>);
  }, [taskList]);

  const importantDataMap = useMemo(() => {
    return importantList?.reduce((pre, cur) => {
      const curMoment = moment(cur.date).format("YYYY-MM-DD");
      pre[curMoment] = cur.issues;
      return pre;
    }, {} as Record<ImportantData["date"], ImportantData["issues"]>);
  }, [importantList]);

  const pickerValue = useMemo(() => {
    return moment(value);
  }, [value]);

  useEffect(() => {
    const now = moment(defaultSelectedDate || value);
    const formatDate = now.format("YYYY-MM-DD");
    const curBriefData = briefDataMap?.[formatDate];
    const curTaskData = taskDataMap?.[formatDate];
    const curImportantData = importantDataMap?.[formatDate];
    const curData = {
      brief: curBriefData,
      task: curTaskData,
      importance: curImportantData,
    };
    setSelectedData({ date: now, data: curData });
  }, [briefDataMap, defaultSelectedDate, importantDataMap, taskDataMap, value]);

  const dateRender = useCallback(
    (date: Moment) => {
      let solar2lunarData;
      if (showLunarInfo) {
        solar2lunarData = solarLunar.solar2lunar(
          date.year(),
          date.month() + 1,
          date.date()
        );
      }
      const formatDate = date.format("YYYY-MM-DD");
      const curBriefData = briefDataMap?.[formatDate];
      const curTaskData = taskDataMap?.[formatDate];
      const curImportantData = importantDataMap?.[formatDate];
      const taskColor =
        taskSettings?.colorMap?.[
          min(
            curTaskData?.map((task) =>
              get(task, taskSettings?.fields?.priority)
            )
          )
        ];
      const importanceColor =
        importanceSettings?.colorMap?.[
          importanceSettings?.priority?.find((type) =>
            curImportantData?.includes(type)
          )
        ];
      return (
        <div
          className={classNames(styles.dateContainer, {
            [styles.importantDay]: !!importanceColor,
            [styles.today]: date.isSame(pickerValue, "date"),
          })}
          style={{
            borderColor: date.isSame(selectedData.date, "date")
              ? "var(--color-auxiliary-text)"
              : importanceColor,
            backgroundColor: importanceColor,
            height: dateCellHeight,
          }}
        >
          {curBriefData && (
            <div className={styles.briefText}>{curBriefData}</div>
          )}
          {!isNil(taskColor) && (
            <div
              className={styles.taskPoint}
              style={{
                backgroundColor: taskColor,
              }}
            ></div>
          )}
          <div className={styles.dateMain}>
            <div className={styles.dateNumber}>{date.date()}</div>
            {showLunarInfo && (
              <div className={styles.dateText}>{solar2lunarData.dayCn}</div>
            )}
          </div>
        </div>
      );
    },
    [
      briefDataMap,
      taskDataMap,
      importantDataMap,
      taskSettings,
      importanceSettings,
      selectedData,
      pickerValue,
      dateCellHeight,
      showLunarInfo,
    ]
  );

  const extraFooterNode = useMemo(() => {
    if (isEmpty(selectedData)) return;
    const {
      date,
      data: { importance, task },
    } = selectedData;
    return (
      <div className={styles.calendarFooter} style={footerStyle}>
        <div className={styles.dateInfo}>
          <div className={styles.dateText}>{date.format("LL")}</div>
          {importance?.length > 0 && !isEmpty(importanceSettings) && (
            <div>
              {importance?.map((issues) => (
                <span
                  className={styles.importantItem}
                  key={issues}
                  style={{
                    backgroundColor: importanceSettings.colorMap?.[issues],
                  }}
                >
                  {issues}
                </span>
              ))}
            </div>
          )}
        </div>
        {task?.length > 0 && !isEmpty(taskSettings) && (
          <div className={styles.taskInfo}>
            <div className={styles.taskTitle}>{taskSettings.taskTitle}</div>
            <div className={styles.taskList}>
              {task?.map((task: any, index: number) => {
                const taskTime = get(task, taskSettings.fields?.time);
                const { url } = task;
                return (
                  <div
                    className={classNames(styles.taskItem, {
                      [styles.taskLinkItem]: url,
                    })}
                    key={index}
                    onClick={(e) => {
                      url && window.open(url, "_blank");
                    }}
                  >
                    <div
                      className={styles.taskItemColor}
                      style={{
                        backgroundColor:
                          taskSettings.colorMap?.[
                            get(task, taskSettings.fields?.priority)
                          ],
                      }}
                    ></div>
                    {taskTime && (
                      <div className={styles.taskItemTime}>
                        {moment(taskTime).format("YYYY-MM-DD HH:mm")}
                      </div>
                    )}
                    <div className={styles.taskItemText}>
                      {get(task, taskSettings.fields?.summary)}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </div>
    );
  }, [importanceSettings, selectedData, taskSettings, footerStyle]);

  const onSelect = useCallback(
    (date: Moment) => {
      const formatDate = date.format("YYYY-MM-DD");
      const curBriefData = briefDataMap?.[formatDate];
      const curTaskData = taskDataMap?.[formatDate];
      const curImportantData = importantDataMap?.[formatDate];
      const curData = {
        brief: curBriefData,
        task: curTaskData,
        importance: curImportantData,
      };
      setSelectedData({ date, data: curData });
      onDateSelect({
        date: formatDate,
        data: curData,
      });
    },
    [briefDataMap, importantDataMap, onDateSelect, taskDataMap]
  );

  const onPanelChange = useCallback(
    (date: Moment, mode: string) => {
      const formatDate = date.format("YYYY-MM-DD");
      onPickerPanelChange({ mode, date: formatDate });
    },
    [onPickerPanelChange]
  );

  return (
    <div className={styles.taskCalendar}>
      <Calendar
        dateFullCellRender={dateRender}
        onSelect={onSelect}
        onPanelChange={onPanelChange}
        defaultValue={pickerValue}
        panelMode={mode}
      />
      {extraFooterNode}
    </div>
  );
}
Example #11
Source File: calendar.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
Calendar: React.FC<CalendarProps> = React.memo(
  ({
    dateSetup,
    locale,
    viewMode,
    rtl,
    width,
    height,
    columnWidth,
    horizontalRange,
    fontFamily,
    fontSize,
    highlightRange,
    displayWidth,
    scrollX,
    setScrollX,
    svgWidth,
    mousePos,
  }) => {
    const today = new Date();
    const getCalendarValuesForMonth = () => {
      const topValues: ReactChild[] = [];
      const bottomValues: ReactChild[] = [];
      const topDefaultHeight = height * 0.5;
      for (let i = 0; i < dateSetup.dates.length; i++) {
        const date = dateSetup.dates[i];
        const bottomValue = getLocaleMonth(date, locale);
        bottomValues.push(
          <text
            key={bottomValue + date.getFullYear()}
            y={height * 0.8}
            x={columnWidth * i + columnWidth * 0.5}
            className={'erda-gantt-calendar-bottom-text'}
          >
            {bottomValue}
          </text>,
        );
        if (i === 0 || date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()) {
          const topValue = date.getFullYear().toString();
          let xText: number;
          if (rtl) {
            xText = (6 + i + date.getMonth() + 1) * columnWidth;
          } else {
            xText = (6 + i - date.getMonth()) * columnWidth;
          }
          topValues.push(
            <TopPartOfCalendar
              key={topValue}
              value={topValue}
              x1Line={columnWidth * i}
              y1Line={0}
              y2Line={topDefaultHeight}
              xText={xText}
              yText={topDefaultHeight * 0.9}
            />,
          );
        }
      }

      return [topValues, bottomValues];
    };

    const getCalendarValuesForWeek = () => {
      const topValues: ReactChild[] = [];
      const bottomValues: ReactChild[] = [];
      let weeksCount = 1;
      const topDefaultHeight = height * 0.5;
      const { dates } = dateSetup;
      for (let i = dates.length - 1; i >= 0; i--) {
        const date = dates[i];
        let topValue = '';
        if (i === 0 || date.getMonth() !== dates[i - 1].getMonth()) {
          // top
          topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`;
        }
        // bottom
        const bottomValue = `W${getWeekNumberISO8601(date)}`;

        bottomValues.push(
          <text
            key={date.getTime()}
            y={height * 0.8}
            x={columnWidth * (i + +rtl)}
            className={'erda-gantt-calendar-bottom-text'}
          >
            {bottomValue}
          </text>,
        );

        if (topValue) {
          // if last day is new month
          if (i !== dates.length - 1) {
            topValues.push(
              <TopPartOfCalendar
                key={topValue}
                value={topValue}
                x1Line={columnWidth * i + weeksCount * columnWidth}
                y1Line={0}
                y2Line={topDefaultHeight}
                xText={columnWidth * i + columnWidth * weeksCount * 0.5}
                yText={topDefaultHeight * 0.9}
              />,
            );
          }
          weeksCount = 0;
        }
        weeksCount++;
      }
      return [topValues, bottomValues];
    };
    const reHighlightRange = {
      ...highlightRange,
      ...(highlightRange?.id && !highlightRange.start && !highlightRange.end ? { x1: -1, x2: -1 } : {}),
    };
    const HoverBar = ({ style }: { style: Obj }) =>
      highlightRange ? (
        <div
          className="absolute rounded bg-black-06"
          style={{
            width: Math.abs(reHighlightRange.x2 - reHighlightRange.x1),
            height: 40,
            left: min([reHighlightRange.x1, reHighlightRange.x2]),
            top: 26,
            ...style,
          }}
        />
      ) : null;

    const HoverTime = ({ style }: { style: Obj }) =>
      mousePos ? (
        <div
          className="absolute rounded bg-black-06"
          style={{
            width: columnWidth,
            height: 40,
            left: mousePos[0] * columnWidth,
            top: 26,
            ...style,
          }}
        />
      ) : null;

    const onChangeScrollX = (direction: number) => {
      const moveLen = Math.floor(displayWidth / columnWidth) - 4; // less then display count;
      const moveX = moveLen > 0 ? moveLen * columnWidth : columnWidth;
      if (direction === -1) {
        setScrollX((prevX) => (moveX >= prevX ? -1 : prevX - moveX));
      } else if (direction === 1) {
        setScrollX((prevX) => (moveX + prevX + displayWidth >= svgWidth ? svgWidth - displayWidth : prevX + moveX));
      }
    };

    const getCalendarValuesForDay = () => {
      let bottomValues: React.ReactNode = null;
      const dates = dateSetup.dates.slice(...horizontalRange);
      const dateInWeeks = [];
      // append date when screen have more space
      if (!dates.length) return null;
      let appendDateLength = Math.max(0, horizontalRange[1] - horizontalRange[0] - dates.length);
      while (appendDateLength-- > 0) {
        const lastDayInLastWeek = dates[dates.length - 1];
        dates.push(addToDate(lastDayInLastWeek, 1, 'day'));
      }
      const firstDay = dates[0];

      const firstDayInWeek = firstDay.getDay();
      // use Monday as first day of week

      const firstWeek = dates.splice(0, firstDayInWeek === 0 ? 1 : 7 - firstDayInWeek + 1);
      while (firstWeek.length < 7) {
        const firstDayInFirstWeek = firstWeek[0];
        firstWeek.unshift(addToDate(firstDayInFirstWeek, -1, 'day'));
      }
      dateInWeeks.push(firstWeek);
      while (dates.length) {
        dateInWeeks.push(dates.splice(0, 7));
      }
      const lastWeek = dateInWeeks[dateInWeeks.length - 1];
      while (lastWeek.length < 7) {
        const lastDayInLastWeek = lastWeek[lastWeek.length - 1];
        lastWeek.push(addToDate(lastDayInLastWeek, 1, 'day'));
      }

      const offsetX = (firstDayInWeek ? firstDayInWeek - 1 : 6) * columnWidth;
      bottomValues = (
        <div
          className="flex h-full w-full erda-gantt-calendar-header-container"
          style={{ transform: `translateX(${-offsetX}px)` }}
        >
          {<HoverBar style={{ transform: `translateX(${offsetX}px)` }} />}
          {<HoverTime style={{ transform: `translateX(${offsetX}px)` }} />}
          {flatten(dateInWeeks).map((day, idx) => {
            const mark =
              reHighlightRange?.x1 === columnWidth * idx - offsetX ||
              reHighlightRange?.x2 === columnWidth * (idx + 1) - offsetX;
            const cls = `${
              mark
                ? 'calendar-highlight-text'
                : `${[0, 6].includes(day.getDay()) ? 'calendar-disabled-text' : 'calendar-normal-text'}`
            }`;
            const isStartPos = columnWidth * idx - offsetX === 0;
            const isToday = moment(day).isSame(today, 'day');
            return (
              <div
                key={day.getTime()}
                style={{
                  width: columnWidth,
                  height: 40,
                  top: 28,
                  left: columnWidth * idx,
                }}
                className={`absolute flex flex-col items-center text-xs justify-center ${cls} ${
                  isToday ? 'text-red' : ''
                }`}
              >
                <span>{Days[day.getDay()]}</span>
                <span>{day.getDate()}</span>
                {isStartPos || day.getDate() === 1 ? (
                  <div className="absolute text-default-8 font-medium " style={{ top: -16 }}>
                    {Months[day.getMonth()]}
                  </div>
                ) : null}
                {isToday ? (
                  <div
                    style={{ left: (columnWidth + 22) / 2, bottom: -14 }}
                    className="absolute erda-gantt-calendar-today flex justify-center"
                  >
                    <div>{i18n.t('dop:Today')}</div>
                  </div>
                ) : null}
              </div>
            );
          })}
          {scrollX > 0 ? (
            <div
              className="flex items-center erda-gantt-calendar-arrow-left"
              onClick={() => onChangeScrollX(-1)}
              style={{ left: offsetX }}
            >
              <ErdaIcon type="zuofan" className="ml-1" size={10} />
            </div>
          ) : null}
          {displayWidth + scrollX < svgWidth ? (
            <div
              className="flex items-center erda-gantt-calendar-arrow-right"
              onClick={() => onChangeScrollX(1)}
              style={{ left: offsetX + displayWidth - 16 }}
            >
              <ErdaIcon type="youfan" className="ml-1" size={10} />
            </div>
          ) : null}
        </div>
      );
      return bottomValues;
    };

    const getCalendarValuesForOther = () => {
      const topValues: ReactChild[] = [];
      const bottomValues: ReactChild[] = [];
      const ticks = viewMode === ViewMode.HalfDay ? 2 : 4;
      const topDefaultHeight = height * 0.5;
      const { dates } = dateSetup;
      for (let i = 0; i < dates.length; i++) {
        const date = dates[i];
        const bottomValue = getCachedDateTimeFormat(locale, {
          hour: 'numeric',
        }).format(date);

        bottomValues.push(
          <text
            key={date.getTime()}
            y={height * 0.8}
            x={columnWidth * (i + +rtl)}
            className={'erda-gantt-calendar-bottom-text'}
            fontFamily={fontFamily}
          >
            {bottomValue}
          </text>,
        );
        if (i === 0 || date.getDate() !== dates[i - 1].getDate()) {
          const topValue = `${date.getDate()} ${getLocaleMonth(date, locale)}`;
          topValues.push(
            <TopPartOfCalendar
              key={topValue + date.getFullYear()}
              value={topValue}
              x1Line={columnWidth * i + ticks * columnWidth}
              y1Line={0}
              y2Line={topDefaultHeight}
              xText={columnWidth * i + ticks * columnWidth * 0.5}
              yText={topDefaultHeight * 0.9}
            />,
          );
        }
      }

      return [topValues, bottomValues];
    };

    // let topValues: ReactChild[] = [];
    // let bottomValues: ReactChild[] = [];
    // switch (dateSetup.viewMode) {
    //   // case ViewMode.Month:
    //   //   [topValues, bottomValues] = getCalendarValuesForMonth();
    //   //   break;
    //   // case ViewMode.Week:
    //   //   [topValues, bottomValues] = getCalendarValuesForWeek();
    //   //   break;
    //   case ViewMode.Day:
    //     [topValues, bottomValues] = getCalendarValuesForDay();
    //     break;
    //   default:
    //     [topValues, bottomValues] = getCalendarValuesForOther();
    //     break;
    // }
    const finalWidth = max([columnWidth * dateSetup.dates.length, width]);

    return (
      <svg
        xmlns="http://www.w3.org/2000/svg"
        className="overflow-visible"
        width={width}
        height={height}
        fontFamily={fontFamily}
      >
        <g className="erda-gantt-calendar" fontSize={fontSize}>
          <rect x={0} y={0} width={finalWidth} height={height} className={'erda-gantt-calendar-header'} />
          <foreignObject
            x={0}
            y={0}
            width={finalWidth}
            height={height}
            className={'erda-gantt-calendar-header overflow-visible'}
          >
            {getCalendarValuesForDay()}
          </foreignObject>
          {/* {topValues} */}
        </g>
      </svg>
    );
  },
)
Example #12
Source File: grid-body.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
GridBody: React.FC<GridBodyProps> = ({
  tasks: originTasks,
  dates,
  barTasks: tasks,
  rowHeight,
  svgWidth,
  columnWidth,
  todayColor,
  selectedTask,
  ganttHeight,
  setSelectedTask,
  rtl,
  onDateChange,
  ganttEvent,
  setRangeAddTime,
  horizontalRange,
  displayWidth,
  onMouseMove: propsOnMouseMove,
  mouseUnFocus: propsMouseUnFocus,
  mousePos,
}) => {
  let y = 0;
  const gridRows: ReactChild[] = [];
  const today = new Date();
  const dateDelta =
    dates[1].getTime() -
    dates[0].getTime() -
    dates[1].getTimezoneOffset() * 60 * 1000 +
    dates[0].getTimezoneOffset() * 60 * 1000;

  const [startPos, setStartPos] = React.useState<null | number[]>(null);
  const [endPos, setEndPos] = React.useState<null | number[]>(null);
  const [chosenTask, setChosenTask] = React.useState<Obj | null>(null);

  React.useEffect(() => {
    if (startPos && endPos) {
      setRangeAddTime({ x1: startPos[0], x2: endPos[0] });
    } else {
      setRangeAddTime(null);
    }
  }, [startPos, endPos]);

  const onMouseDown = (e: React.MouseEvent) => {
    const gridPos = e.currentTarget.getBoundingClientRect();
    const clickY = e.clientY - gridPos.y;
    const clickPos = Math.floor(clickY / rowHeight);
    const curTask = tasks[clickPos];

    if (!curTask.start || !curTask.end) {
      setSelectedTask(curTask.id);
      setChosenTask(curTask);

      setStartPos([
        Math.floor((e.clientX - gridPos.x) / columnWidth) * columnWidth,
        clickPos * rowHeight + 8,
        e.clientX - gridPos.x,
      ]);
    }
  };
  const mouseUnFocus = () => {
    propsMouseUnFocus();
    setStartPos(null);
    setEndPos(null);
    setChosenTask(null);
  };
  const curDates = dates.slice(...horizontalRange);
  const todayIndex = findIndex(curDates, (item) => moment(item).isSame(today, 'day'));
  const addTime = getDateFormX(startPos?.[0], endPos?.[0], dateDelta, columnWidth, curDates[0]?.getTime());

  const onMouseUp = () => {
    if (addTime.length && addTime[1] - addTime[0] >= dateDelta * 0.6 && chosenTask) {
      onDateChange({ ...chosenTask, start: new Date(addTime[0]), end: new Date(addTime[1]) });
    }
    mouseUnFocus();
  };
  const onMouseMove = (e: React.MouseEvent) => {
    const gridPos = e.currentTarget.getBoundingClientRect();
    propsOnMouseMove(e);
    const curEndPod = e.clientX - gridPos.x;

    if (startPos) {
      setEndPos(
        curEndPod - startPos[2] > 10
          ? [
              (Math.floor((e.clientX - gridPos.x + 1) / columnWidth) + 1) * columnWidth,
              startPos[1] + rowHeight - 16,
              curEndPod,
            ]
          : null,
      );
    }
  };

  tasks?.forEach((task: Task, idx: number) => {
    const validTask = task.start && task.end;
    let PointIcon = null;
    if (validTask) {
      const displayPos = Math.floor(displayWidth / columnWidth);
      if (curDates?.[0] && task.end < curDates[0]) {
        PointIcon = (
          <div className="text-default-2 hover:text-default-4 erda-gantt-grid-arrow-box flex items-center">
            <ErdaIcon className="cursor-pointer " type="zuo" size={20} onClick={() => setSelectedTask(task.id)} />
            <div className="erda-gantt-grid-arrow text-default-6">
              {moment(task.start).format('MM-DD')} ~ {moment(task.end).format('MM-DD')}
            </div>
          </div>
        );
      } else if (curDates?.[displayPos] && task.start > curDates[displayPos]) {
        PointIcon = (
          <div
            className="text-default-2 hover:text-default-4 erda-gantt-grid-arrow-box flex items-center"
            style={{ marginLeft: displayWidth - 20 - 80 }}
          >
            <div className="erda-gantt-grid-arrow text-default-6">
              {moment(task.start).format('MM-DD')} ~ {moment(task.end).format('MM-DD')}
            </div>
            <ErdaIcon className="cursor-pointer" onClick={() => setSelectedTask(task.id)} type="you" size={20} />
          </div>
        );
      }
    }
    gridRows.push(
      <foreignObject key={`Row${task.id}`} x="0" y={y} width={svgWidth} height={rowHeight}>
        <div
          className={`flex erda-gantt-grid-row h-full ${
            selectedTask?.id === task.id ? 'erda-gantt-grid-row-selected' : ''
          } ${!validTask ? 'on-add' : ''} ${mousePos?.[1] === idx ? 'on-hover' : ''}`}
        />
      </foreignObject>,
    );
    y += rowHeight;
  });

  const { changedTask } = ganttEvent || {};
  const realHeight = tasks.length * rowHeight;

  const getAddRangePos = () => {
    if (startPos && endPos) {
      return {
        transform: `translate(${min([startPos[0], endPos[0]])},${min([startPos[1], endPos[1]])})`,
        width: Math.abs(startPos[0] - endPos[0]),
        height: Math.abs(startPos[1] - endPos[1]),
      };
    }
    return null;
  };

  const getRangePos = () => {
    if (changedTask) {
      return {
        transform: `translate(${changedTask.x1},0)`,
        width: changedTask.x2 - changedTask.x1,
        height: max([ganttHeight, realHeight]),
      };
    } else if (startPos && endPos) {
      return {
        transform: `translate(${min([startPos[0], endPos[0]])},0)`,
        width: Math.abs(endPos[0] - startPos[0]),
        height: max([ganttHeight, realHeight]),
      };
    }
    return null;
  };

  const getMouseBlockPos = () => {
    if (mousePos) {
      const curTask = tasks[mousePos[1]];
      if (curTask && !curTask.start && !curTask.end) {
        return {
          width: columnWidth,
          height: rowHeight - 16,
          transform: `translate(${mousePos[0] * columnWidth},${mousePos[1] * rowHeight + 8})`,
        };
      }
    }
    return null;
  };

  const getMouseHoverPos = () => {
    if (mousePos) {
      return {
        width: 8,
        height: max([ganttHeight, realHeight]),
        transform: `translate(${mousePos[0] * columnWidth + columnWidth / 2 - 4},0)`,
      };
    }
    return null;
  };

  const rangePos = getRangePos();
  const mouseBlockPos = getMouseBlockPos();
  const addRangePos = getAddRangePos();
  const mouseHoverPos = getMouseHoverPos();
  const todayStartPos = todayIndex * columnWidth + columnWidth / 2 - 1;
  return (
    <g
      className="gridBody"
      onMouseDown={onMouseDown}
      onMouseUp={() => {
        onMouseUp();
      }}
      onMouseMove={onMouseMove}
      onMouseLeave={mouseUnFocus}
    >
      {rangePos ? (
        <rect {...rangePos} className="erda-gantt-grid-changed-range" />
      ) : mouseHoverPos ? (
        <foreignObject {...mouseHoverPos}>
          <div className="h-full w-full erda-gantt-grid-hover-box">
            <div className="erda-gantt-grid-hover-arrow" />
            <div className="erda-gantt-grid-hover-range h-full w-full" />
          </div>
        </foreignObject>
      ) : null}

      <g className="rows">{gridRows}</g>
      {addRangePos ? (
        <g>
          <foreignObject {...addRangePos}>
            <div className="erda-gantt-grid-add-rect text-sm text-desc  bg-white bg-opacity-100 w-full h-full">{`${moment(
              addTime[0],
            ).format('MM-DD')}~${moment(addTime[1]).format('MM-DD')}`}</div>
          </foreignObject>
        </g>
      ) : mouseBlockPos ? (
        <g>
          <foreignObject {...mouseBlockPos}>
            <div className="erda-gantt-grid-add-rect bg-white bg-opacity-100 w-full h-full" />
          </foreignObject>
        </g>
      ) : null}
      {todayIndex > -1 ? (
        <polyline
          points={`${todayStartPos + columnWidth / 2},4 ${todayStartPos + columnWidth / 2},${max([
            ganttHeight,
            realHeight,
          ])}`}
          className="erda-gantt-grid-today"
        />
      ) : null}
    </g>
  );
}
Example #13
Source File: contextMenuItems.ts    From gant-design with MIT License 4 votes vote down vote up
gantGetcontextMenuItems = function(
  params: GetContextMenuItemsParams,
  config: ContextMenuItemsConfig,
) {
  const {
    downShift,
    locale,
    onRowsCut,
    onRowsPaste,
    getContextMenuItems,
    defaultJsonParams = {},
    hideMenuItemExport,
    hideMenuItemExpand,
    hiddenMenuItemNames,
    suppressRightClickSelected,
    showCutChild,
  } = config;
  const {
    context: {
      globalEditable,
      treeData,
      createConfig,
      getRowNodeId,
      gridManager,
      showCut,
      rowSelection,
    },
    node,
    api,
  } = params;
  const exportJson = !isEmpty(defaultJsonParams);
  const rowIndex = get(node, 'rowIndex', 0);
  let selectedRowNodes: RowNode[] = api.getSelectedNodes();
  //右键选中⌚️
  if (node && !suppressRightClickSelected) {
    const rowNodes = api.getSelectedNodes();
    if (!downShift || rowNodes.length == 0) {
      node.setSelected(true, true);
      selectedRowNodes = [node];
    } else {
      const rowNodeIndexs = rowNodes.map(rowNode => rowNode.rowIndex);
      const maxIndex = max(rowNodeIndexs);
      const minIndex = min(rowNodeIndexs);
      if (rowIndex >= minIndex && rowIndex <= maxIndex) {
        node.setSelected(true, true);
        selectedRowNodes = [node];
      } else {
        const isMin = rowIndex < minIndex;
        const nodesCount = isMin ? minIndex - rowIndex : rowIndex - maxIndex;
        const startIndex = isMin ? rowIndex : maxIndex + 1;
        const extraNodes = Array(nodesCount)
          .fill('')
          .map((item, index) => {
            const startNode = api.getDisplayedRowAtIndex(index + startIndex);
            startNode.setSelected(true);
            return startNode;
          });
        selectedRowNodes = isMin ? [...extraNodes, ...rowNodes] : [...rowNodes, ...extraNodes];
      }
    }
  }
  const gridSelectedKeys: string[] = [];
  const gridSelectedRows = selectedRowNodes.map(item => {
    gridSelectedKeys.push(getRowNodeId(get(item, 'data', {})));
    return item.data;
  }, []);
  const disabledCut = selectedRowNodes.length <= 0 || (treeData && isEmpty(createConfig));
  const hasPaste =
    selectedRowNodes.length > 1 ||
    (treeData && isEmpty(createConfig)) ||
    isEmpty(gridManager.cutRows);
  let items = getContextMenuItems
    ? getContextMenuItems({
        selectedRows: gridSelectedRows,
        selectedKeys: gridSelectedKeys,
        selectedRowKeys: gridSelectedKeys,
        ...params,
      } as any)
    : [];

  if (hiddenMenuItemNames && hiddenMenuItemNames.length) {
    remove(items, menuItem => hiddenMenuItemNames.some(menuName => menuName === menuItem.name));
  }

  let defultMenu = [];
  if (treeData && !hideMenuItemExpand) {
    defultMenu = ['expandAll', 'contractAll'];
  }
  defultMenu =
    defultMenu.length > 0
      ? items.length > 0
        ? [...defultMenu, ...items]
        : defultMenu
      : [...items];
  if (!hideMenuItemExport) {
    defultMenu = defultMenu.length > 0 ? [...defultMenu, 'export'] : ['export'];
    if (suppressRightClickSelected) {
      defultMenu.push({
        name: locale.exportSelected,
        icon: '<span class="ag-icon ag-icon-save" unselectable="on" role="presentation"></span>',
        action: () => {
          api.exportDataAsExcel({
            onlySelected: true,
          });
        },
      });
    }
  }

  defultMenu = exportJson
    ? [
        ...defultMenu,
        {
          name: locale.exportJson,
          action: () => {
            const { title = 'gantdGrid', onlySelected } = defaultJsonParams;
            let data = [];
            if (onlySelected) {
              data = api.getSelectedRows();
            } else {
              api.forEachNode(node => {
                if (node.data) data.push(node.data);
              });
            }
            const jsonBlob = new Blob([JSON.stringify(data)], {
              type: 'text/plain;charset=utf-8',
            });
            FileSaver.saveAs(jsonBlob, `${title}.json`);
          },
        },
      ]
    : defultMenu;

  if (!globalEditable) return defultMenu;

  defultMenu = exportJson
    ? [
        ...defultMenu,
        {
          name: locale.importJson,
          action: () => {
            const { coverData } = defaultJsonParams;
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = 'application/json';
            input.onchange = (event: any) => {
              const [file] = event.target.files;
              const reader = new FileReader();
              reader.readAsText(file);
              reader.onload = function(event: any) {
                try {
                  const update = [],
                    add = [];
                  const json = JSON.parse(event.target.result);
                  if (coverData) {
                    api.setRowData(json);
                    gridManager.reset();
                    return;
                  }
                  json.map((itemData: any) => {
                    const rowNode = api.getRowNode(getRowNodeId(itemData));
                    if (rowNode && rowNode.data) {
                      update.push({ ...itemData, ...rowNode.data });
                    } else add.push(itemData);
                  });
                  api.applyTransactionAsync({ update }, () => {
                    gridManager.create(add);
                  });
                } catch (error) {}
              };
            };
            input.click();
          },
        },
      ]
    : defultMenu;
  const showCutBtns = typeof showCut === 'function' ? showCut(params) : showCut;

  const editMenu = [...defultMenu];
  if (showCutBtns) {
    editMenu.push(
      ...[
        {
          name: locale.cutRows,
          disabled: disabledCut,
          action: params => {
            try {
              const canPut = onRowsCut ? onRowsCut(selectedRowNodes) : true;
              return canPut && gridManager.cut(selectedRowNodes);
            } catch (error) {}
          },
        },
        {
          name: locale.cancelCut,
          disabled: isEmpty(gridManager.cutRows),
          action: params => {
            try {
              gridManager.cancelCut();
            } catch (error) {}
          },
        },
        {
          name: locale.pasteTop,
          disabled: hasPaste,
          action: params => {
            const [rowNode] = selectedRowNodes;
            const canPaste = onRowsPaste ? onRowsPaste(gridManager.cutRows, rowNode) : true;
            canPaste && gridManager.paste(rowNode);
          },
        },
        {
          name: locale.pasteBottom,
          disabled: hasPaste,
          action: params => {
            const [rowNode] = selectedRowNodes;
            const canPaste = onRowsPaste ? onRowsPaste(gridManager.cutRows, rowNode) : true;
            canPaste && gridManager.paste(rowNode, false);
          },
        },
      ],
    );
    if (showCutChild)
      editMenu.push({
        name: locale.pasteChild,
        disabled: hasPaste,
        action: params => {
          const [rowNode] = selectedRowNodes;
          const canPaste = onRowsPaste ? onRowsPaste(gridManager.cutRows, rowNode) : true;
          canPaste && gridManager.paste(rowNode, false, true);
        },
      });
  }
  return editMenu;
}