All files / src/utils ComponentUtils.tsx

87.5% Statements 63/72
64.7% Branches 44/68
75% Functions 12/16
87.32% Lines 62/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208                                                241x               2x 2x 2x 2x 2x 2x                                         11x 11x 11x 6x     6x 4x 4x           4x 2x   2x 2x 2x   2x 2x               4x   6x   11x                               445x 445x 445x   445x 445x 445x 445x 445x       445x       445x                                     445x     445x     66x 445x 445x   445x     66x 518x 518x 518x   518x   518x     66x 39x     66x 518x 518x     66x 9x     66x       66x           66x 28x           66x 6x     66x       66x 1x    
import React, { Key, ReactElement } from 'react';
import {
  MailOutlined,
  QuestionCircleOutlined,
} from '@ant-design/icons';
import { Tooltip } from 'antd';
import { ColumnType, TableMetaProps, TableMode } from '@props/RecordProps';
import { getLinkStrRegExp, getMailStrRegExp, humanReadableTitle } from './StringUtils';
import {
  columnsFullPathWidth,
  columnsTypeDefaultWidth,
  columnsTypeWidth,
  getColumnTransKey,
  isActionColumn,
  isDynamicField,
  isObjectType,
  tableColumnMaxWidth
} from '@utils/ColumnsUtils';
import { CustomIcon, FileFieldType } from '@config/base';
import ReactDOMServer from 'react-dom/server';
import i18n from "@config/i18n";
import i18next from 'i18next';
 
export function getReadOnlyClass(updatable: boolean | undefined): string {
  return (updatable == null || updatable) ? "" : "readonly";
}
 
/**
 * Estimate column width based on length of the column title be to displayed
 * @param title title of the column to display on table header
 */
export function estimateColumnWidthBasedOnTitle(title: string): number {
  const thFontSize = 14;
  const thFontWeight = 1.0;
  const characterWidthHeightRatio = 0.9;
  const leftPadding = 32;
  const rightPadding = 32;
  return title.length * thFontWeight * thFontSize * characterWidthHeightRatio + leftPadding + rightPadding;
}
 
export interface CalculateTableAndColumnWidthResult {
  columnLength: number;
  columns: Array<TableMetaProps>;
}
 
/**
 * ATTENTION: This method will update the columns parameter
 * Calculate width of column and update attributes in the column meta array
 * Return length of all the columns as result
 * 自动列宽度的计算规则:
 * 1. 按照 title 的字符数,经过经验值估算
 * 2. 按照 columnsTypeWidth 中配置的宽度, 会分别以 column 的 key 和 type 为
 *    查找条件进行查找,使用 key 为条件查找的优先级高于使用 type 为条件查找的
 * 3. 对以上 1 和 2 计算和查找到的宽度进行比较,取较大的值
 * @param domainName domain Name
 * @param columns columns meta data
 */
export function calculateTableAndColumnWidth(domainName: string, columns: Array<TableMetaProps>): CalculateTableAndColumnWidthResult {
  const result: TableMetaProps[] = [];
  let length = 0;
  for (let i = 0; i < columns.length; i++) {
    const column = {
      ...columns[i],
    };
    if (!isActionColumn(column.key)) {
      const thisKey = isObjectType(column.type) ? "object" : column["key"] as ColumnType;
      const lastColumn = (i === (columns.length - 1));
      // ********************* IMPORTANT NOTICE *************************************/
      // If it's last column and width less than max width, we don't set width,
      // this is to make sure action and id column will have it's defined width
      // Otherwise antd will auto set the table column width for them and destroy the layout
      // ********************* IMPORTANT NOTICE *************************************/
      if (!lastColumn || (length > tableColumnMaxWidth)) {
        const lengthBasedOnTitle = estimateColumnWidthBasedOnTitle(
          column.title ?? i18n.t(getColumnTransKey(domainName, column.key)));
        const columnPathWidthValue = columnsFullPathWidth[`${domainName}.${thisKey}`];
        const keyWidthValue = columnsTypeWidth[thisKey];
        Iif (columnPathWidthValue != null) {
          column["width"] = (lengthBasedOnTitle > columnPathWidthValue) ? lengthBasedOnTitle : columnPathWidthValue;
        } else if (keyWidthValue != null) {
          column["width"] = (lengthBasedOnTitle > keyWidthValue) ? lengthBasedOnTitle : keyWidthValue;
        } else E{
          const thisType = column["type"];
          const widthValue = columnsTypeWidth[thisType];
          const defaultWidth = widthValue ?? columnsTypeDefaultWidth;
          column["width"] = (lengthBasedOnTitle > defaultWidth) ? lengthBasedOnTitle : defaultWidth;
        }
      }
      length += (column["width"] == null) ? 0 : Number.parseInt(column["width"].toString());
    }
    result.push(column);
  }
  return {
    columns: result,
    columnLength: length,
  };
}
 
export function getFieldTitle(
  props: {
    domainName: string;
    helpText: string | undefined;
    key: string;
    title: string;
    unique?: boolean | Array<string>;
    email?: boolean;
  }
): ReactElement | string {
  const { title, helpText, key, unique, email, domainName } = props;
  const transKey = getColumnTransKey(domainName, key);
  const transTitle = isDynamicField(key) || !i18next.exists(transKey) ?
    title : i18n.t(transKey);
  const readableTitle = humanReadableTitle(transTitle);
  const helpTextNotEmpty = (helpText != null && helpText !== '');
  const uniqueNotEmpty = (unique != null);
  const uniqueKey = getUniqueKeyTitle(transTitle, unique);
  const uniqueIcon = uniqueNotEmpty ?
    <Tooltip title={uniqueKey}>
      <CustomIcon type="icon-weiyi" title={uniqueKey} />
    </Tooltip> : "";
  const emailIcon = (email) ?
    <Tooltip title="Should be a valid email address">
      <MailOutlined />
    </Tooltip> : "";
  const vWithHelpText = (uniqueNotEmpty) ? (
    <span>
      <Tooltip title={replaceLink(helpText)}>
        {readableTitle}
      </Tooltip>
      &nbsp;{uniqueIcon} {emailIcon}
      <Tooltip title={replaceLink(helpText)}>
        &nbsp;<QuestionCircleOutlined />
      </Tooltip>
    </span>
  ) : (
      <>
        <Tooltip title={replaceLink(helpText)}>
          {readableTitle}&nbsp;<QuestionCircleOutlined />
        </Tooltip>
        {emailIcon}
      </>
    );
 
  const vWithoutHelpText = ((uniqueNotEmpty) ? (
    <label title={transTitle}>{readableTitle} {uniqueIcon} {emailIcon}</label>
  ) : readableTitle);
  return helpTextNotEmpty ? vWithHelpText : vWithoutHelpText;
}
 
export const getUniqueKeyTitle = (title: string, unique?: boolean | string[]): string => {
  const uniqueTitle = `Field ${title} should be unique`;
  const uniqueTitleArr = (JSON.stringify(unique) !== "[]") ?
    `${uniqueTitle}, together with ${JSON.stringify(unique)}` : `${uniqueTitle}`;
  return (unique != null) ? uniqueTitleArr : title;
};
 
export const getContentByLinkRule = (text: string | undefined): { __html: string } => {
  const linkRule = getLinkStrRegExp();
  const mailRule = getMailStrRegExp();
  const link = text?.replace(linkRule, ("<a href='$1' target='_blank'>$1 " + ReactDOMServer.renderToString(
    <CustomIcon type='icon-link' />) + "</a>"));
  const mailLink = link?.replace(
    mailRule, ("<a href='mailto:$1' target='_blank'>$1 " + ReactDOMServer.renderToString(<MailOutlined />) + "</a>"));
  return { __html: mailLink == null ? "" : mailLink };
};
 
export const wrapAsHtml = (message: string | undefined | null, key?: Key): ReactElement => {
  return <slot dangerouslySetInnerHTML={{ __html: message ?? "" }} key={key} />;
};
 
export const replaceLink = (text: string | undefined): ReactElement => {
  const element = getContentByLinkRule(text);
  return <span><slot dangerouslySetInnerHTML={element} /></span>;
};
 
export const displaySingleResult = (mode: string): boolean => {
  return ['OBJECT_MULTIPLE', 'CLASS_LEVEL', 'OBJECT_SINGLE'].includes(mode);
};
 
export const getFaviconEl = (): HTMLLinkElement | null => {
  return document.getElementById("favicon") as HTMLLinkElement;
};
 
export const supportDnd = (columns: Array<TableMetaProps>): boolean => {
  return (columns != null) &&
    (columns.length > 0) &&
    (columns.find(c => c.key === 'displaySequence') != null);
};
 
export const isMultipleFileColumn = (column: TableMetaProps): boolean => {
  return (column != null)
    && (column.type === 'array')
    && (column.elementType === FileFieldType);
};
 
//FIXME Combile below 3 methods
export const shouldDisplayIcon = (page: string, tableMode?: TableMode): boolean => {
  return page === 'LIST' || page === 'INLINE_DISPLAY' || tableMode === 'card-list';
};
 
export const displayHasDetailPlaceholder = (page: string, tableMode: string | undefined): boolean => {
  return page != 'LIST' && tableMode != 'detail-drawer' && tableMode !== 'card-list';
};
 
export const notDisplayHasDetailPlaceholder = (page: string, tableMode: string | undefined): boolean => {
  return (page === 'LIST' || page === 'INLINE_DISPLAY' || tableMode == 'detail-drawer' || tableMode === 'card-list');
};