lodash#keys TypeScript Examples

The following examples show how to use lodash#keys. 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: utils.ts    From redux-with-domain with MIT License 7 votes vote down vote up
// get state by path
// parent module's data aside in .base
export function getStateByNamespace(state, namespace, initialState) {
  const path = toStorePath(namespace)
  const initialStateKeys = keys(initialState)
  const findState = get(state, path)
  if (findState === undefined) {
    throw Error(`Please check if you forget to add module ${path} `)
  }

  if (isEmpty(initialState)) {
    if (findState['@@loading']) {
      return findState
    }
    return get(state, `${path}.base`) // not in base
  }
  let isModuleState = true
  initialStateKeys.forEach(key => {
    if (!has(findState, key)) {
      isModuleState = false
    }
  })
  if (isModuleState) return findState
  return get(state, `${path}.base`)
}
Example #2
Source File: data-set-operate.ts    From S2 with MIT License 6 votes vote down vote up
flattenDeep = (data: Record<any, any>[] | Record<any, any>) =>
  keys(data)?.reduce((pre, next) => {
    const item = get(data, next);
    if (Array.isArray(item)) {
      pre = pre.concat(flattenDeep(item));
    } else {
      pre?.push(item);
    }

    return pre;
  }, [])
Example #3
Source File: utils.ts    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
getTranslateAddonName = (name: string) => (keys(addonNameMap).includes(name) ? addonNameMap[name] : name)
Example #4
Source File: utils.ts    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
getTranslateAddonList = (addonList: ADDON.Instance[], key: string) =>
  map(addonList, (item) => {
    const currentItem = { ...item };
    if (keys(addonNameMap).includes(item[key])) {
      currentItem[key] = addonNameMap[item[key]];
    }
    return currentItem;
  })
Example #5
Source File: form.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
genValidateFn =
  (cb = noop) =>
  (item: InnerFormField, data: any) => {
    for (const rule of item.rules) {
      const checkList = getCheckListFromRule(rule);
      for (const check of checkList) {
        const v = get(data, item.key);
        let result = check(v, rule, data); // 返回 [status: 'success' | 'error', msg: '']
        if (!item.required && !isNumber(v) && isEmpty(v) && ['min', 'len'].includes(Object.keys(rule)[0])) {
          result = [true, ''];
        }
        if (isPromise(result)) {
          // @ts-ignore
          result.then(cb);
          return ['validating', '异步校验中...', result];
        } else if (result[0] === false) {
          return ['error', result[1]];
        } else if (typeof result[0] === 'string') {
          return result;
        }
      }
    }
    return ['success'];
  }
Example #6
Source File: assetsDbService.ts    From nautilus-wallet with MIT License 6 votes vote down vote up
public async sync(assets: IDbAsset[], walletId: number): Promise<void> {
    const groups = groupBy(assets, (a) => a.address);
    const dbGroups = groupBy(await this.getByWalletId(walletId), (a) => a.address);
    const groupKeys = union(keys(dbGroups), keys(groups));

    for (const key of groupKeys) {
      const group = groups[key];
      const dbGroup = dbGroups[key];

      if (isEmpty(dbGroup) && isEmpty(groups)) {
        continue;
      } else if (isEmpty(group) && !isEmpty(dbGroup)) {
        await dbContext.assets.bulkDelete(this.primaryKeysFrom(dbGroup));
        continue;
      } else if (isEmpty(dbGroup)) {
        await dbContext.assets.bulkPut(group);
        continue;
      }

      const remove = this.primaryKeysFrom(differenceBy(dbGroup, group, (a) => a.tokenId));
      const put = this.newOrChanged(dbGroup, group);
      if (remove.length > 0) {
        await dbContext.assets.bulkDelete(remove);
      }
      if (put.length > 0) {
        await dbContext.assets.bulkPut(put);
      }
    }
  }
Example #7
Source File: sort-action.ts    From S2 with MIT License 6 votes vote down vote up
getSortByMeasureValues = (
  params: SortActionParams,
): DataType[] => {
  const { dataSet, sortParam, originValues } = params;
  const { fields } = dataSet;
  const { sortByMeasure, query, sortFieldId } = sortParam;

  if (sortByMeasure !== TOTAL_VALUE) {
    // 按指标只排序 - 最内侧的行列不需要汇总后排序
    return dataSet.getMultiData(query);
  }

  const isRow =
    fields?.columns?.includes(sortFieldId) &&
    keys(query)?.length === 1 &&
    has(query, EXTRA_FIELD);

  // 按 data 数据中的小计,总计排序
  const measureValues = dataSet.getMultiData(query, true, isRow);
  if (measureValues && !isEmpty(measureValues)) {
    return measureValues;
  }
  // 按前端的小计,总计排序
  return map(originValues, (originValue) => {
    const totalParams = createTotalParams(originValue, fields, sortFieldId);

    return (dataSet as PivotDataSet).getTotalValue({
      ...query,
      ...totalParams,
    });
  });
}
Example #8
Source File: sort-action.ts    From S2 with MIT License 6 votes vote down vote up
createTotalParams = (
  originValue: string,
  fields: Fields,
  sortFieldId: string,
) => {
  const totalParams = {};
  const isMultipleDimensionValue = includes(originValue, ID_SEPARATOR);

  if (isMultipleDimensionValue) {
    // 获取行/列小计时,需要将所有行/列维度的值作为 params
    const realOriginValue = split(originValue, ID_SEPARATOR);
    const keys = fields?.rows?.includes(sortFieldId)
      ? fields.rows
      : fields.columns;

    for (let i = 0; i <= indexOf(keys, sortFieldId); i++) {
      totalParams[keys[i]] = realOriginValue[i];
    }
  } else {
    totalParams[sortFieldId] = originValue;
  }
  return totalParams;
}
Example #9
Source File: data-set-operate.ts    From S2 with MIT License 6 votes vote down vote up
flatten = (data: Record<any, any>[] | Record<any, any>) => {
  let result = [];
  if (Array.isArray(data)) {
    keys(data)?.forEach((item) => {
      const current = get(data, item);
      if (keys(current)?.includes('undefined')) {
        keys(current)?.forEach((ki) => {
          result.push(current[ki]);
        });
      } else {
        result = result.concat(current);
      }
    });
  } else {
    result = result.concat(data);
  }
  return result;
}
Example #10
Source File: pivot-data-set.ts    From S2 with MIT License 6 votes vote down vote up
public getCellData(params: CellDataParams): DataType {
    const { query, rowNode, isTotals = false } = params || {};

    const { columns, rows: originRows } = this.fields;
    let rows = originRows;
    const drillDownIdPathMap =
      this.spreadsheet?.store.get('drillDownIdPathMap');

    // 判断当前是否为下钻节点
    // 需检查 rowNode.id 是否属于下钻根节点(drillDownIdPathMap.keys)的下属节点
    const isDrillDown = Array.from(drillDownIdPathMap?.keys() ?? []).some(
      (parentPath) => rowNode.id.startsWith(parentPath),
    );

    // 如果是下钻结点,小计行维度在 originRows 中并不存在
    if (!isTotals || isDrillDown) {
      rows = Node.getFieldPath(rowNode, isDrillDown) ?? originRows;
    }
    const rowDimensionValues = getQueryDimValues(rows, query);
    const colDimensionValues = getQueryDimValues(columns, query);
    const path = getDataPath({
      rowDimensionValues,
      colDimensionValues,
      careUndefined:
        isTotals || isTotalData([].concat(originRows).concat(columns), query),
      rowPivotMeta: this.rowPivotMeta,
      colPivotMeta: this.colPivotMeta,
    });
    const data = get(this.indexesData, path);
    if (data) {
      // 如果已经有数据则取已有数据
      return data;
    }

    return isTotals ? this.getTotalValue(query) : data;
  }
Example #11
Source File: download-ui5-resources.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
async function getLibs(version: TestModelVersion): Promise<string[]> {
  // The metadata.json seems to have been added only very recently :(
  // For now we assume the libraries are the same in all versions, when we support newer versions we should
  // do a better check here
  let versionInMetadataURL: string = version;
  if (versionInMetadataURL !== "1.76.0") {
    versionInMetadataURL = "1.76.0";
  }
  const url = `https://unpkg.com/@sapui5/distribution-metadata@${versionInMetadataURL}/metadata.json`;
  const response = await fetch(url);
  if (!response.ok) {
    log(`error fetching from ${url}`);
    return [];
  }
  const fileContent = await response.text();
  const librariesMetadata = JSON.parse(fileContent);
  return keys(librariesMetadata.libraries);
}
Example #12
Source File: TranslationMap.ts    From mo360-ftk with MIT License 6 votes vote down vote up
public static convert(map: ITranslationMap): ITranslation[] {
        const languages: string[] = keys(map);
        let translationIds: string[] = [];
        const translations: ITranslation[] = [];

        forEach(languages, language => {
            translationIds = uniq(translationIds.concat(keys(get(map, language, []))));
        });

        forEach(translationIds, translationId => {
            const translation: ITranslation = {
                messages: new Registry<IMessage>(),
                translationId,
            };

            forEach(languages, lang => {
                const message = get(map, [lang, translationId], null);

                if (message === null) {
                    throw new Error(`missing translationId "${translationId}" in ${lang}`);
                }

                translation.messages.add(lang, { lang, message });
            });

            translations.push(translation);
        });

        return translations;
    }
Example #13
Source File: factory.ts    From S2 with MIT License 5 votes vote down vote up
// 缓存内置 Icon 信息
keys(InternalSvgIcons).forEach((name) => {
  registerIcon(name, InternalSvgIcons[name]);
});
Example #14
Source File: user.ts    From ovineherd with Apache License 2.0 5 votes vote down vote up
export function getOrgLimit(action: string, opts?: any): any {
  const isRoot = userInfo.isOrgRoot

  // 应用 apps 相关的权限解析
  const { id: appId = '', pathPrefix = '' } = opts || {}

  const info: any = {}
  const orgLimit = userInfo.org_limit || {}

  const limitStr = keys(orgLimit).join(',') || ''
  const appActions = ['loginApp', 'editApp', 'designApp', 'delApp']

  // apps 页面的权限
  if (action === 'apps') {
    info.addApp = isRoot || orgLimit['orgApp/addApp']
    if (appId) {
      const prefix = `app/${appId}/`
      appActions.forEach((act) => {
        if (isRoot) {
          info.viewApp = true
          info[act] = true
          return
        }
        if ((orgLimit[`orgApp/${act}`] && !orgLimit[`${prefix}ignore`]) || orgLimit[prefix + act]) {
          info.viewApp = true
          info[act] = true
        }
      })
      if (!info.viewApp && info.addApp && !orgLimit[`${prefix}ignore`]) {
        info.viewApp = true
      }
    }
  }

  // 页面相关权限
  if (action === 'pages') {
    info.application = isRoot || new RegExp(['addApp'].concat(appActions).join('|')).test(limitStr)
    info.team = isRoot || /orgTeam/.test(limitStr)
    info.role = isRoot || /orgRole/.test(limitStr)
    info.setting = true // 一定会有权限

    if (pathPrefix) {
      map(info, (isVisible: boolean, key: string) => {
        if (isVisible && !info.redirect) {
          info.redirect = `${pathPrefix}${key}`
        }
      })
    }
  }

  return info
}
Example #15
Source File: settings-config-spec.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
describe("settings configuration properties", () => {
  let packageJsonSettings: Record<string, Setting>;

  before(() => {
    // Get the settings from the package.json
    const packageJsonPath = require.resolve(
      "vscode-ui5-language-assistant/package.json"
    );
    const packageJsonContent = readJsonSync(packageJsonPath);
    packageJsonSettings =
      packageJsonContent.contributes.configuration.properties;
  });

  it("default setting values in package.json have the correct type", () => {
    forEach(packageJsonSettings, (value, key) => {
      expect(
        typeof value.default,
        `Setting ${key} default value type does not match the setting's defined type`
      ).to.equal(value.type);
    });
  });

  it("settings in package.json are in sync with the settings package", () => {
    const defaultSettingsFromPackageJson = parseSettings(packageJsonSettings);
    const defaultSettings = getDefaultSettings();
    expect(
      defaultSettingsFromPackageJson,
      "settings from package.json don't match the default settings in the language server"
    ).to.deep.equal({
      UI5LanguageAssistant: defaultSettings,
    });
  });

  it("enums in package.json are in sync with the settings package", () => {
    const pkgJsonSettingsWithEnum = pickBy(packageJsonSettings, (_) =>
      has(_, "enum")
    );
    forEach(pkgJsonSettingsWithEnum, (pkgJsonSetting, settingsKey) => {
      const settingsModulePropName = camelCase(
        settingsKey.replace(
          /UI5LanguageAssistant.(\w+)\.(\w+)/,
          "valid $1 $2 values"
        )
      );
      const settingsModulePropValue = keys(
        settingsModule[settingsModulePropName]
      );
      const pkgJsonPropValue = pkgJsonSetting.enum;
      expect(settingsModulePropValue).to.deep.equalInAnyOrder(pkgJsonPropValue);
    });
  });

  it("use the correct logging configuration property name", () => {
    expect(packageJsonSettings[LOGGING_LEVEL_CONFIG_PROP]).to.exist;
    expect(
      packageJsonSettings[LOGGING_LEVEL_CONFIG_PROP].description
    ).to.include("logging");
  });

  type Setting = {
    scope: string;
    type: string;
    default: unknown;
    description: string;
    enum?: string[];
  };

  function parseSettings(properties: Record<string, Setting>): unknown {
    const defaultSettings = {};
    forEach(properties, (value, key) => {
      set(defaultSettings, key, value.default);
    });
    return defaultSettings;
  }
});
Example #16
Source File: monitor-chart-panel.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
MonitorChartPanel = (props: IProps) => {
  const [showNumber, setShowNumber] = React.useState(4);
  const { resourceType, resourceId, metrics, commonChartQuery = {}, defaultTime } = props;
  if (!resourceType || !resourceId) {
    return null;
  }
  const displayMetrics = slice(values(metrics), 0, showNumber);
  return (
    <>
      <div className="monitor-chart-time-container">
        <TimeSelector defaultTime={defaultTime} />
      </div>
      <div className="monitor-chart-panel">
        {map(displayMetrics, ({ parameters, name: metricKey, title, metricName, unit, unitType }) => {
          const chartQuery = {
            ...commonChartQuery,
            fetchMetricKey: metricKey,
          };
          if (!isEmpty(parameters)) {
            map(Object.keys(parameters), (key) => {
              chartQuery[key] = parameters[key];
            });
          }
          return (
            <div className="monitor-chart-cell spin-full-height" key={metricKey}>
              <MonitorChart
                {...{ resourceType, resourceId }}
                title={title || metricName}
                metricKey={metricKey}
                metricUnitType={unitType}
                metricUnit={unit}
                chartQuery={chartQuery}
              />
            </div>
          );
        })}
        <IF check={keys(metrics).length > showNumber}>
          <div className="show-all" onClick={() => setShowNumber((prevNumber) => prevNumber + 4)}>
            {allWordsFirstLetterUpper(i18n.t('load more'))}
          </div>
        </IF>
      </div>
    </>
  );
}
Example #17
Source File: issue-type-manage.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
IssueTypeManage = () => {
  const [issueTimeMap] = issueFieldStore.useStore((s) => [s.issueTimeMap]);
  const { getIssueTime, getFieldsByIssue } = issueFieldStore.effects;
  const { clearFieldList } = issueFieldStore.reducers;
  const { id: orgID } = orgStore.useStore((s) => s.currentOrg);

  useMount(() => {
    getIssueTime({ orgID });
  });

  const [{ modalVisible, issueType }, updater, update] = useUpdate({
    modalVisible: false,
    issueType: 'COMMON' as ISSUE_FIELD.IIssueType,
  });

  const list = React.useMemo(() => {
    return map(keys(ISSUE_LIST_MAP), (t) => {
      return {
        ...ISSUE_LIST_MAP[t],
        updatedTime: issueTimeMap[t],
      };
    });
  }, [issueTimeMap]);

  const formData = {};
  const fieldsList: object[] = [];

  const onClose = () => {
    getIssueTime({ orgID });
    updater.modalVisible(false);
    clearFieldList();
  };

  const readonlyForm = (
    <div>
      {map(list, (item) => {
        return (
          <div
            className="panel hover-active-bg"
            key={item.type}
            onClick={() => {
              getFieldsByIssue({ propertyIssueType: item.type, orgID });
              update({
                modalVisible: true,
                issueType: item.type as ISSUE_FIELD.IIssueType,
              });
            }}
          >
            <div className="common-list-item">
              <div className="list-item-left">
                <div className="flex justify-between items-center">
                  <IssueIcon type={item.type} withName />
                </div>
                <div className="sub">
                  <span>{i18n.t('Update time')}:</span>
                  <span>{item.updatedTime || i18n.t('dop:Not modified')}</span>
                </div>
              </div>
            </div>
          </div>
        );
      })}
      <IssueFieldSettingModal visible={modalVisible} issueType={issueType} closeModal={onClose} />
    </div>
  );

  return (
    <SectionInfoEdit
      hasAuth={false}
      data={formData}
      readonlyForm={readonlyForm}
      fieldsList={fieldsList}
      updateInfo={getIssueTime}
      name={i18n.t('dop:Custom configuration of issue type')}
      desc={i18n.t(
        'dop:Mainly manages the configuration of issue attributes, saved as the issue template configuration for the entire organization-level project.',
      )}
    />
  );
}
Example #18
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
ChartsResult = () => {
  const [s, setS] = React.useState(0);
  const { relsCount = {} } = testPlanStore.useStore((state) => state.planReport);

  React.useEffect(() => {
    setTimeout(() => setS(1), 0);
  });

  const layout = React.useMemo(() => {
    const nameMap = {};
    const color: string[] = [];
    const names: string[] = [];
    const data: Array<{ name: string; value: number }> = [];
    map(keys(nameMaps), (key) => {
      const value = get(relsCount, key);
      if (value) {
        const name = nameMaps[key];
        nameMap[key] = nameMaps[key];
        color.push(colorMap[name]);
        names.push(name);
        data.push({ name, value });
      }
    });
    const staticData = {
      legendData: names,
      metricData: [{ name: i18n.t('dop:results of the'), data }],
      extraOption: {
        color,
      },
    };
    return [
      {
        w: 24,
        h: 10,
        x: 0,
        y: 0,
        i: 'view-pie',
        moved: false,
        static: false,
        view: {
          name: i18n.t('dop:Result distribution of use case execution'),
          chartType: 'chart:pie',
          // hideHeader: true,
          staticData,
          config: {
            option: {
              legend: {
                orient: 'horizontal',
                x: 'center',
                y: 'bottom',
              },
              color,
              series: [
                {
                  center: ['50%', '45%'],
                  radius: ['0%', '55%'],
                  label: {
                    formatter: '{b}: {c} ({d}%)',
                  },
                },
              ],
            },
          },
        },
      },
    ];
  }, [relsCount]);

  if (s === 0) {
    return null;
  }
  return <BoardGrid.Pure layout={layout} />;
}
Example #19
Source File: Registry.ts    From mo360-ftk with MIT License 5 votes vote down vote up
/**
   * @returns {string[]}
   */
  public getKeys(): string[] {
    return keys(this.keyValueMap);
  }
Example #20
Source File: Registry.ts    From mo360-ftk with MIT License 5 votes vote down vote up
/**
   * @param {(value: T, index: string) => void} callback
   */
  public forEach(callback: (value: T, index: string) => void) {
    keys(this.keyValueMap).map((key) => {
      callback(this.get(key), key);
    });
  }
Example #21
Source File: pivot-data-set.ts    From S2 with MIT License 5 votes vote down vote up
public getMultiData(
    query: DataType,
    isTotals?: boolean,
    isRow?: boolean,
    drillDownFields?: string[],
  ): DataType[] {
    if (isEmpty(query)) {
      return compact(customFlattenDeep(this.indexesData));
    }
    const { rows, columns, values: valueList } = this.fields;
    const totalRows = !isEmpty(drillDownFields)
      ? rows.concat(drillDownFields)
      : rows;
    const rowDimensionValues = getQueryDimValues(totalRows, query);
    const colDimensionValues = getQueryDimValues(columns, query);
    const path = getDataPath({
      rowDimensionValues,
      colDimensionValues,
      careUndefined: true,
      isFirstCreate: true,
      rowFields: rows,
      colFields: columns,
      rowPivotMeta: this.rowPivotMeta,
      colPivotMeta: this.colPivotMeta,
    });
    const currentData = this.getCustomData(path);
    let result = compact(customFlatten(currentData));
    if (isTotals) {
      // 总计/小计(行/列)
      // need filter extra data
      // grand total =>  {$$extra$$: 'price'}
      // sub total => {$$extra$$: 'price', category: 'xxxx'}
      // [undefined, undefined, "price"] => [category]
      let fieldKeys = [];
      const rowKeys = getFieldKeysByDimensionValues(rowDimensionValues, rows);
      const colKeys = getFieldKeysByDimensionValues(
        colDimensionValues,
        columns,
      );
      if (isRow) {
        // 行总计
        fieldKeys = rowKeys;
      } else {
        // 只有一个值,此时为列总计
        const isCol = keys(query)?.length === 1 && has(query, EXTRA_FIELD);

        if (isCol) {
          fieldKeys = colKeys;
        } else {
          const getTotalStatus = (dimensions: string[]) => {
            return isEveryUndefined(
              dimensions?.filter((item) => !valueList?.includes(item)),
            );
          };
          const isRowTotal = getTotalStatus(colDimensionValues);
          const isColTotal = getTotalStatus(rowDimensionValues);

          if (isRowTotal) {
            // 行小计
            fieldKeys = rowKeys;
          } else if (isColTotal) {
            // 列小计
            fieldKeys = colKeys;
          } else {
            // 行小计+列 or 列小计+行
            fieldKeys = [...rowKeys, ...colKeys];
          }
        }
      }
      result = result.filter(
        (r) =>
          !fieldKeys?.find(
            (item) => item !== EXTRA_FIELD && keys(r)?.includes(item),
          ),
      );
    }

    return result || [];
  }
Example #22
Source File: pivot-data-set.ts    From S2 with MIT License 5 votes vote down vote up
public getDimensionValues(field: string, query?: DataType): string[] {
    const { rows = [], columns = [] } = this.fields || {};
    let meta: PivotMeta = new Map();
    let dimensions: string[] = [];
    if (includes(rows, field)) {
      meta = this.rowPivotMeta;
      dimensions = rows;
    } else if (includes(columns, field)) {
      meta = this.colPivotMeta;
      dimensions = columns;
    }

    if (!isEmpty(query)) {
      let sortedMeta = [];
      const dimensionValuePath = [];
      for (const dimension of dimensions) {
        const value = get(query, dimension);
        dimensionValuePath.push(`${value}`);
        const cacheKey = dimensionValuePath.join(`${ID_SEPARATOR}`);
        if (meta.has(value) && !isUndefined(value)) {
          const childField = meta.get(value)?.childField;
          meta = meta.get(value).children;
          if (
            find(this.sortParams, (item) => item.sortFieldId === childField) &&
            this.sortedDimensionValues[childField]
          ) {
            const dimensionValues = this.sortedDimensionValues[
              childField
            ]?.filter((item) => item?.includes(cacheKey));
            sortedMeta = getDimensionsWithoutPathPre([...dimensionValues]);
          } else {
            sortedMeta = [...meta.keys()];
          }
        }
      }
      if (isEmpty(sortedMeta)) {
        return [];
      }
      return filterUndefined(getListBySorted([...meta.keys()], sortedMeta));
    }

    if (this.sortedDimensionValues[field]) {
      return filterUndefined(
        getDimensionsWithoutPathPre([...this.sortedDimensionValues[field]]),
      );
    }

    return filterUndefined([...meta.keys()]);
  }
Example #23
Source File: custom-tree-pivot-data-set.ts    From S2 with MIT License 5 votes vote down vote up
processDataCfg(dataCfg: S2DataConfig): S2DataConfig {
    // 自定义行头有如下几个特点
    // 1、rows配置必须是空,需要额外添加 $$extra$$ 定位数据(标记指标的id)
    // 2、要有配置 fields.rowCustomTree(行头结构)
    // 3、values 不需要参与计算,默认就在行头结构中
    dataCfg.fields.rows = [EXTRA_FIELD];
    dataCfg.fields.valueInCols = false;
    const { data, ...restCfg } = dataCfg;
    const { values } = dataCfg.fields;
    // 将源数据中的value值,映射为 $$extra$$,$$value$$
    // {
    // province: '四川',    province: '四川',
    // city: '成都',   =>   city: '成都',
    // price='11'           price='11'
    //                      $$extra$$=price
    //                      $$value$$=11
    // 此时 province, city 均配置在columns里面
    // }
    const transformedData = [];
    forEach(data, (dataItem) => {
      if (isEmpty(intersection(keys(dataItem), values))) {
        transformedData.push(dataItem);
      } else {
        forEach(values, (value) => {
          if (has(dataItem, value)) {
            transformedData.push({
              ...dataItem,
              [EXTRA_FIELD]: value,
              [VALUE_FIELD]: dataItem[value],
            });
          }
        });
      }
    });

    return {
      data: uniq(transformedData),
      ...restCfg,
    };
  }
Example #24
Source File: base-cell.ts    From S2 with MIT License 5 votes vote down vote up
// 根据当前state来更新cell的样式
  public updateByState(stateName: InteractionStateName, cell: S2CellType) {
    this.spreadsheet.interaction.setInteractedCells(cell);
    const stateStyles = get(
      this.theme,
      `${this.cellType}.cell.interactionState.${stateName}`,
    );

    const { x, y, height, width } = this.getCellArea();

    each(stateStyles, (style, styleKey) => {
      const targetShapeNames = keys(
        pickBy(SHAPE_ATTRS_MAP, (attrs) => includes(attrs, styleKey)),
      );
      targetShapeNames.forEach((shapeName: StateShapeLayer) => {
        const isStateShape = this.stateShapes.has(shapeName);
        const shape = isStateShape
          ? this.stateShapes.get(shapeName)
          : this[shapeName];

        // stateShape 默认 visible 为 false
        if (isStateShape && !shape.get('visible')) {
          shape.set('visible', true);
        }

        // 根据borderWidth更新borderShape大小 https://github.com/antvis/S2/pull/705
        if (
          shapeName === 'interactiveBorderShape' &&
          styleKey === 'borderWidth'
        ) {
          if (isNumber(style)) {
            const marginStyle = {
              x: x + style / 2,
              y: y + style / 2,
              width: width - style - 1,
              height: height - style - 1,
            };
            each(marginStyle, (currentStyle, currentStyleKey) => {
              updateShapeAttr(shape, currentStyleKey, currentStyle);
            });
          }
        }
        updateShapeAttr(shape, SHAPE_STYLE_MAP[styleKey], style);
      });
    });
  }
Example #25
Source File: WalletForm.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
export function WalletForm() {
  const { t } = useTranslation();
  const { accounts, api, network, chain } = useApi();
  const { contacts } = useContacts();
  const [form] = useForm();
  const history = useHistory();
  const [selectedAccounts, setSelectedAccounts] = useState<string[]>([]);
  const [shareScope, setShareScope] = useState<ShareScope>(ShareScope.all);
  const mainColor = useMemo(() => {
    return getThemeColor(network);
  }, [network]);

  const presetNetworks = useMemo(() => {
    return keys(chains);
  }, []);

  const options = useMemo<{ label: string; value: string }[]>(() => {
    const accountOptions = accounts?.map(({ address, meta }) => ({
      label: meta?.name ? `${meta?.name} - ${address}` : address,
      value: address,
    }));
    const contactOptions = contacts?.map(({ address, meta }) => ({
      label: meta?.name ? `${meta?.name} - ${address}` : address,
      value: address,
    }));
    const composeOptions: { label: string; value: string }[] = [];
    if (accountOptions) {
      composeOptions.push(...accountOptions);
    }
    if (contactOptions) {
      composeOptions.push(...contactOptions);
    }

    return composeOptions.filter(({ value }) => !selectedAccounts.includes(value)) || [];
  }, [accounts, contacts, selectedAccounts]);

  const updateSelectedAccounts = (namePath?: (string | number)[]) => {
    const selected: {
      name: string;
      address: string;
    }[] = form.getFieldValue('members') || [];
    let result = selected.map((item) => item?.address);

    if (namePath) {
      const value = form.getFieldValue(namePath);

      result = result.filter((item) => item !== value);
    }

    setSelectedAccounts(result);
  };

  const uploadProps = {
    name: 'file',
    headers: {
      authorization: 'authorization-text',
    },
    onChange(info: any) {
      if (info.file.status !== 'uploading') {
        // console.log(info.file, info.fileList);
      }
      if (info.file.status === 'done') {
        message.success(`${info.file.name} file uploaded successfully`);
      } else if (info.file.status === 'error') {
        message.error(`${info.file.name} file upload failed.`);
      }
    },
    customRequest(info: any) {
      try {
        const reader = new FileReader();

        reader.onload = (e: any) => {
          // eslint-disable-next-line no-console
          // console.log(e.target.result);

          const config = JSON.parse(e.target.result) as MultisigAccountConfig;
          if (!config || !config.members || !config.threshold) {
            message.error(t('account config error'));
            return;
          }
          const encodeMembers = config.members.map((member) => {
            return {
              name: member.name,
              address: encodeAddress(member.address, Number(chain.ss58Format)),
            };
          });
          form.setFieldsValue({ threshold: config.threshold, name: config.name, members: encodeMembers });
        };
        reader.readAsText(info.file);
      } catch (err: unknown) {
        message.error(t('account config error'));
        if (err instanceof Error) {
          // eslint-disable-next-line no-console
          console.log('err:', err);
        }
      }
    },
  };

  return (
    <Form
      name="wallet"
      layout="vertical"
      validateMessages={validateMessages[i18n.language as 'en' | 'en-US' | 'zh-CN' | 'zh']}
      form={form}
      initialValues={{
        name: '',
        threshold: 2,
        members: [
          { name: '', address: '' },
          { name: '', address: '' },
          { name: '', address: '' },
        ],
        rememberExternal: true,
      }}
      onFinish={async (values: WalletFormValue) => {
        const { members, name, threshold, rememberExternal } = values;
        const signatories = members.map(({ address }) => address);
        const addressPair = members.map(({ address, ...other }) => ({
          ...other,
          address: encodeAddress(address, Number(chain.ss58Format)),
        }));
        // Add external address to contact list.
        const addExternalToContact = () => {
          members.forEach((item) => {
            const account = accounts?.find((accountItem) => {
              return accountItem.address === item.address;
            });
            const contact = contacts?.find((contactItem) => {
              return contactItem.address === item.address;
            });

            if (!account && !contact) {
              try {
                keyring.saveAddress(item.address, {
                  name: item.name,
                });
              } catch (err: unknown) {
                if (err instanceof Error) {
                  message.error(err.message);
                }
              }
            }
          });
        };
        const exec = () => {
          try {
            keyring.addMultisig(signatories, threshold, {
              name,
              addressPair,
              genesisHash: api?.genesisHash.toHex(),
            });

            if (rememberExternal) {
              addExternalToContact();
            }

            updateMultiAccountScope(values, network);
            message.success(t('success'));
            history.push('/' + history.location.hash);
          } catch (error: unknown) {
            if (error instanceof Error) {
              message.error(t(error.message));
            }
          }
        };

        const acc = findMultiAccount(values);

        if (acc) {
          confirmToAdd(acc, exec);
        } else {
          exec();
        }
      }}
      className="max-w-screen-xl mx-auto"
    >
      <Form.Item>
        <div className="w-full grid grid-cols-4 items-center gap-8">
          <Upload {...uploadProps} showUploadList={false}>
            <Button type="primary" size="middle" block className="flex justify-center items-center">
              {t('import from config')}
            </Button>
          </Upload>
        </div>
      </Form.Item>

      <Form.Item
        name="name"
        label={<LabelWithTip name="name" tipMessage="wallet.tip.name" />}
        rules={[{ required: true }]}
      >
        <Input size="large" />
      </Form.Item>

      <Form.Item
        name="threshold"
        label={<LabelWithTip name="threshold" tipMessage="wallet.tip.threshold" />}
        rules={[{ required: true }]}
      >
        <InputNumber size="large" min={THRESHOLD} className="w-full" />
      </Form.Item>

      <Form.Item label={<LabelWithTip name="share scope" tipMessage="wallet.tip.share" />}>
        <div className="flex items-center">
          <Form.Item name="share" rules={[{ required: true }]} initialValue={1} className="mb-0">
            <Radio.Group onChange={(event) => setShareScope(event.target.value)}>
              <Radio value={ShareScope.all}>{t('All Networks')}</Radio>
              <Radio value={ShareScope.current}>{t('Current Network')}</Radio>
              <Radio value={ShareScope.custom}>{t('Custom')}</Radio>
            </Radio.Group>
          </Form.Item>

          {shareScope === ShareScope.custom && (
            <Form.Item name="scope" rules={[{ required: true }]} initialValue={[network]} className="mb-0 flex-1">
              <Select mode="multiple" disabled={shareScope !== ShareScope.custom}>
                {presetNetworks.map((net) => (
                  <Select.Option value={net} key={net}>
                    <Tag
                      color={getThemeColor(net as Network)}
                      style={{
                        borderRadius: '2px',
                      }}
                    >
                      {net}
                    </Tag>
                  </Select.Option>
                ))}
              </Select>
            </Form.Item>
          )}
        </div>
      </Form.Item>

      <LabelWithTip name="members" tipMessage="wallet.tip.members" />

      <Row gutter={20} className="bg-gray-100 mt-2 mb-6 p-4">
        <Col span={2}>{t('id')}</Col>
        <Col span={5}>{t('name')}</Col>
        <Col span={17}>{t('address')}</Col>
      </Row>

      <Form.List name="members">
        {(fields, { add, remove }) => (
          <>
            {fields.map((field, index) => (
              <Row key={field.key} gutter={20} className="px-4">
                <Col span={2} className="pl-2 pt-2">
                  {index + 1}
                </Col>
                <Col span={5}>
                  <Form.Item
                    {...field}
                    name={[field.name, 'name']}
                    fieldKey={[field.key, 'name']}
                    rules={[{ required: true, message: t('Member name is required') }]}
                  >
                    <Input size="large" placeholder={t('wallet.tip.member_name')} className="wallet-member" />
                  </Form.Item>
                </Col>
                <Col span={16}>
                  <Form.Item
                    {...field}
                    name={[field.name, 'address']}
                    fieldKey={[field.key, 'address']}
                    validateFirst
                    rules={[
                      { required: true, message: t('Account address is required') },
                      {
                        validator: (_, value) =>
                          convertToSS58(value, Number(chain.ss58Format)) ? Promise.resolve() : Promise.reject(),
                        message: t('You must input a ss58 format address'),
                      },
                    ]}
                  >
                    <AutoComplete
                      options={options}
                      onChange={(addr) => {
                        let account: KeyringAddress | InjectedAccountWithMeta | undefined = accounts?.find(
                          (item) => item.address === addr
                        );

                        if (!account) {
                          account = contacts?.find((item) => item.address === addr);
                        }

                        if (!account) {
                          return;
                        }

                        const members: { name?: string; address: string }[] = form.getFieldValue('members');

                        if (account) {
                          members[index].name = account?.meta?.name ?? '';
                          form.setFieldsValue({ members: [...members] });
                        }

                        setSelectedAccounts(members.map((item) => item?.address));
                      }}
                    >
                      <Input
                        suffix={<img src={iconDownFilled} alt="down" />}
                        size="large"
                        placeholder={t('wallet.tip.member_address')}
                        className="wallet-member"
                      />
                    </AutoComplete>
                  </Form.Item>
                </Col>

                <Col span={1}>
                  <Form.Item>
                    <DeleteOutlined
                      className="text-xl mt-2"
                      style={{
                        color: mainColor,
                      }}
                      onClick={() => {
                        updateSelectedAccounts(['members', field.name, 'address']);

                        if (fields.length > THRESHOLD) {
                          remove(field.name);
                        } else {
                          const members = form.getFieldValue('members');

                          members[index] = { name: '', address: '' };
                          form.setFieldsValue({ members: [...members] });
                          message.warn(`You must set at least ${THRESHOLD} members.`);
                        }
                      }}
                    />
                  </Form.Item>
                </Col>
              </Row>
            ))}

            <Row>
              <Col span={24}>
                <Form.Item>
                  <Button
                    size="large"
                    onClick={() => add()}
                    block
                    className="flex justify-center items-center w-full"
                    style={{ color: mainColor }}
                  >
                    {t('add_members')}
                  </Button>
                </Form.Item>
              </Col>
            </Row>
          </>
        )}
      </Form.List>

      <Form.Item label={null} name="rememberExternal" valuePropName="checked">
        <Checkbox>{t('contact.Add External Address')}</Checkbox>
      </Form.Item>

      <Form.Item>
        <div className="w-2/5 grid grid-cols-2 items-center gap-8">
          <Button type="primary" size="large" block htmlType="submit" className="flex justify-center items-center">
            {t('create')}
          </Button>
          <Link to="/" className="block">
            <Button
              type="default"
              size="large"
              className="flex justify-center items-center w-full"
              style={{ color: mainColor }}
            >
              {t('cancel')}
            </Button>
          </Link>
        </div>
      </Form.Item>
    </Form>
  );
}
Example #26
Source File: PropertyPicker.tsx    From gio-design with Apache License 2.0 4 votes vote down vote up
PropertyPicker: React.FC<PropertyPickerProps> = (props: PropertyPickerProps) => {
  const {
    value: initialValue,
    searchBar,
    loading = false,
    dataSource: originDataSource,
    recentlyStorePrefix = '_gio',
    onChange,
    onSelect,
    onClick,
    detailVisibleDelay = 600,
    fetchDetailData = (data: PropertyItem): Promise<PropertyInfo> => Promise.resolve({ ...data }),
    disabledValues = [],
    shouldUpdateRecentlyUsed = true,
    className,
    ...rest
  } = props;
  const locale = useLocale('PropertyPicker');
  const localeText = { ...defaultLocale, ...locale } as typeof defaultLocale;
  const Tabs = toPairs(PropertyTypes(localeText)).map((v) => ({ key: v[0], children: v[1] }));
  const [scope, setScope] = useState('all');
  const [keyword, setKeyword] = useState<string | undefined>('');
  const [recentlyUsedInMemo, setRecentlyUsedInMemo] = useState<{
    [key: string]: any[];
  }>();
  const [recentlyUsed, setRecentlyUsed] = useLocalStorage<{
    [key: string]: any[];
  }>(`${recentlyStorePrefix}_propertyPicker`, {
    all: [],
  });

  useEffect(() => {
    if (shouldUpdateRecentlyUsed) {
      setRecentlyUsedInMemo(recentlyUsed);
    }
  }, [recentlyUsed, shouldUpdateRecentlyUsed]);

  const [currentValue, setCurrentValue] = useState<PropertyValue | undefined>(initialValue);

  const prefixCls = usePrefixCls('property-picker-legacy');

  const [detailVisible, setDetailVisible] = useState(false);
  const debounceSetDetailVisible = useDebounceFn((visible: boolean) => {
    setDetailVisible(visible);
  }, detailVisibleDelay);
  const [dataList, setDataList] = useState<PropertyItem[]>([]);
  const navRef = useRef([{ key: 'all', children: localeText.allText }]);
  useEffect(() => {
    // 如果是Dimension类型 需要做一个数据转换
    let propertiItemList: PropertyItem[] = [];
    if (originDataSource && originDataSource.length) {
      if (!('value' in originDataSource[0])) {
        propertiItemList = originDataSource.map((v) => {
          const item = dimensionToPropertyItem(v as Dimension, localeText);
          item.itemIcon = () => <IconRender group={item.iconId} />;
          return item;
        });
      } else {
        propertiItemList = originDataSource.map((v) => {
          const item = v as PropertyItem;
          item.itemIcon = () => <IconRender group={item.iconId} />;
          return item;
        });
      }
    }
    const list = propertiItemList.map((v) => {
      const disabled = !!disabledValues && disabledValues.includes(v.id);

      return {
        ...v,
        disabled,
        pinyinName: getShortPinyin(v.label ?? ''),
      };
    });

    setDataList(list);
    /**
     * 设置属性类型tab,如果传入的列表没有对应的类型 不显示该tab
     */
    const types = uniq(propertiItemList.map((p) => p.type));
    const tabs = Tabs.filter((t) => types.indexOf(t.key) > -1);
    navRef.current = [{ key: 'all', children: localeText.allText }].concat(tabs);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [localeText.allText, originDataSource]);

  /**
   * 搜索关键字的方法,支持拼音匹配
   * @param input 带匹配的项
   * @param key 匹配的关键字
   */
  const keywordFilter = (input = '', key = '') => {
    if (!input || !key) return true;
    return !!pinyinMatch?.match(input, key);
  };

  /**
   * 属性列表数据源
   */
  const dataSource = useMemo(() => {
    const filteredData = dataList.filter((item) => {
      const { label, type, groupId, valueType } = item;
      if (groupId === 'virtual' && valueType !== 'string') {
        return false;
      }
      if (scope === 'all') {
        return keywordFilter(label, keyword);
      }
      return type === scope && keywordFilter(label, keyword);
    });

    // 按照分组排序
    const sortedData = orderBy(filteredData, ['typeOrder', 'groupOrder', 'pinyinName']);

    // mixin 最近使用
    const rids: string[] = recentlyUsedInMemo ? recentlyUsedInMemo[scope] : [];
    const recent: PropertyItem[] = [];
    rids?.forEach((v: string) => {
      const r = filteredData.find((d) => d.value === v);
      if (r) {
        recent.push({
          ...r,
          itemIcon: () => <IconRender group={r.iconId} />,
          _groupKey: 'recently',
        });
      }
    });
    return [recent, sortedData];
  }, [dataList, keyword, recentlyUsedInMemo, scope]);

  function onTabNavChange(key: string) {
    setScope(key);
  }

  /**
   * 点选时 设置最近使用
   * @param item
   */
  function _saveRecentlyByScope(item: PropertyItem) {
    const { value: v, type } = item;
    const recent = cloneDeep(recentlyUsed);
    // save by type/scope
    const realScope = type || 'all';
    let scopedRecent = recent[realScope];
    if (!scopedRecent) {
      scopedRecent = [];
    }
    let newScopedRecent = uniq([v, ...scopedRecent]);
    if (newScopedRecent.length > 5) {
      newScopedRecent = newScopedRecent.slice(0, 5);
    }
    const allScopedRecent = recent.all || [];

    let newAllScopedRecent = uniq([v, ...allScopedRecent]);
    if (newAllScopedRecent.length > 5) {
      newAllScopedRecent = newAllScopedRecent.slice(0, 5);
    }
    recent[realScope] = newScopedRecent;
    recent.all = newAllScopedRecent;
    setRecentlyUsed(recent);
  }
  function handleSelect(node: PropertyItem) {
    setCurrentValue(node as PropertyValue);

    _saveRecentlyByScope(node);
    if (isEmpty(currentValue) || !isEqualWith(currentValue, node, (a, b) => a.value === b.value)) {
      onChange?.(node);
    }
    onSelect?.(node);
  }
  const handleSearch = (query: string) => {
    setKeyword(query);
  };
  const handleItemClick = (e: React.MouseEvent<HTMLElement>, node: PropertyItem) => {
    handleSelect(node);
    onClick?.(e);
  };
  const [recentlyPropertyItems, propertyItems] = dataSource;
  const groupDatasource = useMemo(
    () => groupBy([...propertyItems], (o) => replace(o.type, /^recently¥/, '')),
    [propertyItems]
  );

  function labelRender(item: PropertyItem) {
    const isShowIndent = Boolean(item.associatedKey && item._groupKey !== 'recently');
    return (
      <>
        <span className={classNames('item-icon', { indent: isShowIndent })}>{item.itemIcon?.()}</span>
        <span>{item.label}</span>
      </>
    );
  }
  const [hoverdNodeValue, setHoveredNodeValue] = useState<PropertyItem | undefined>();
  function getListItems(items: PropertyItem[], keyPrefix = '') {
    const handleItemMouseEnter = (data: PropertyItem) => {
      setHoveredNodeValue(data);
      debounceSetDetailVisible(true);
    };
    const handleItemMouseLeave = () => {
      setHoveredNodeValue(undefined);
      debounceSetDetailVisible.cancel();
      setDetailVisible(false);
    };
    const listItems = items.map((data: PropertyItem) => {
      const select =
        !isEmpty(currentValue) &&
        isEqualWith(currentValue, data, (a, b) => a?.value === b?.value) &&
        data._groupKey !== 'recently';
      const itemProp: ListItemProps = {
        disabled: data.disabled,
        ellipsis: true,
        key: ['item', keyPrefix, data.type, data.groupId, data.id].join('-'),
        className: classNames({ selected: select }),
        children: labelRender(data),
        onClick: (e) => handleItemClick(e, data),
        onMouseEnter: () => {
          handleItemMouseEnter(data);
        },
        onMouseLeave: () => {
          handleItemMouseLeave();
        },
      };
      return itemProp;
    });
    return listItems;
  }
  function subGroupRender(groupData: Dictionary<PropertyItem[]>) {
    const dom = keys(groupData).map((gkey) => {
      const { groupName, type } = groupData[gkey][0];
      const listItems = getListItems(groupData[gkey]);
      return (
        <ExpandableGroupOrSubGroup
          key={['exp', type, gkey].join('-')}
          groupKey={[type, gkey].join('-')}
          title={groupName}
          type="subgroup"
          items={listItems}
        />
      );
    });
    return dom as React.ReactNode;
  }
  const renderItems = () => {
    if (propertyItems?.length === 0) {
      return <Result type="empty-result" size="small" />;
    }
    const recentlyNodes = recentlyPropertyItems?.length > 0 && (
      <React.Fragment key="recentlyNodes">
        <ExpandableGroupOrSubGroup
          groupKey="recently"
          key="exp-group-recently"
          title={localeText.recent}
          type="group"
          items={getListItems(recentlyPropertyItems, 'recently')}
        />
        <List.Divider key="divider-group-recently" />
      </React.Fragment>
    );

    const groupFn = (item: PropertyItem, existIsSystem: boolean) => {
      if (existIsSystem) {
        if (item.groupId === 'tag') {
          return 'tag';
        }
        if (item.groupId === 'virtual') {
          return 'virtual';
        }
        return item.isSystem;
      }
      return item.groupId;
    };

    const groupDataNodes = keys(groupDatasource).map((key, index) => {
      const groupData = groupDatasource[key];
      const existIsSystem = has(groupData, '[0].isSystem');

      let subGroupDic;
      if (key === 'event' && 'associatedKey' in groupData[0] && groupData.length > 1) {
        subGroupDic = groupBy(
          groupData
            .filter((ele) => !ele.associatedKey)
            .map((ele) => [ele])
            ?.reduce((acc, cur) => {
              cur.push(
                ...groupData
                  .filter((e) => {
                    if (e.associatedKey === cur[0].id) {
                      if (existIsSystem) {
                        e.isSystem = cur[0].isSystem;
                      }
                      return true;
                    }
                    return false;
                  })
                  .map((item) => {
                    const { groupId, groupName } = [...cur].shift() || {};
                    return { ...item, groupId, groupName };
                  })
              );
              acc.push(...cur);
              return acc;
            }, []),
          (item) => groupFn(item, existIsSystem)
        );
      } else {
        subGroupDic = groupBy(groupData, (item) => groupFn(item, existIsSystem));
      }

      const { typeName } = groupData[0];
      // 此处的处理是 如果2级分组只有一组 提升为一级分组;如果没有这个需求删除该if分支 ;
      if (keys(subGroupDic).length === 1) {
        const items = getListItems(subGroupDic[keys(subGroupDic)[0]]);
        return (
          <React.Fragment key={`groupDataNodes-${index}`}>
            {index > 0 && <List.Divider key={`divider-group-${key}-${index}`} />}
            <ExpandableGroupOrSubGroup
              key={`exp-group-${key}`}
              groupKey={`${key}`}
              title={typeName}
              type="group"
              items={items}
            />
          </React.Fragment>
        );
      }
      return (
        <React.Fragment key={`groupDataNodes-${index}`}>
          {index > 0 && <List.Divider key={`divider-group-${key}-${index}`} />}
          <List.ItemGroup key={`group-${key}`} title={typeName} expandable={false}>
            {subGroupRender(subGroupDic)}
          </List.ItemGroup>
        </React.Fragment>
      );
    });
    const childrens = [recentlyNodes, groupDataNodes];
    return childrens as React.ReactNode;
  };
  const renderDetail = () =>
    hoverdNodeValue && <PropertyCard nodeData={hoverdNodeValue} fetchData={promisify(fetchDetailData)} />;
  return (
    <>
      <BasePicker
        {...rest}
        className={classNames(prefixCls, className)}
        renderItems={renderItems}
        detailVisible={detailVisible && !!hoverdNodeValue}
        renderDetail={renderDetail}
        loading={loading}
        searchBar={{
          placeholder: searchBar?.placeholder || localeText.searchPlaceholder,
          onSearch: handleSearch,
        }}
        tabNav={{
          items: navRef.current,
          onChange: onTabNavChange,
        }}
      />
    </>
  );
}
Example #27
Source File: connectChart.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
ConnectChart = (props) => {
  const {
    data,
    seriseName,
    legendFormatter,
    decimal = 2,
    isBarChangeColor,
    tooltipFormatter,
    yAxisNames = [],
    isLabel,
    noAreaColor,
    timeSpan,
    opt,
  } = props;
  const groups = keys(data.results);
  const [selectedGroup, setSelectedGroup] = React.useState();
  const getOption = () => {
    const moreThanOneDay = timeSpan ? timeSpan.seconds > 24 * 3600 : false;
    const { results: originData, xAxis, time, lines } = data;
    const results = sortBy(originData[selectedGroup] || values(originData)[0], 'axisIndex');
    const legendData = [];
    let yAxis = [];
    const series = [];
    const maxArr = [];

    // 处理markLine
    const markLines = lines || [];
    let markLine = {};
    if (markLines.length) {
      markLine = {
        silent: true,
        label: {
          normal: {
            show: true,
            position: 'middle',
            formatter: (params) => {
              const uType = results[0].unitType;
              const { unit } = results[0];

              const y = getFormatter(uType, unit).format(params.data.yAxis, decimal || 2);
              return `${params.name}: ${y}`;
            },
          },
        },
        data: markLines.map(({ name, value }) => [
          { x: '7%', yAxis: value, name },
          { x: '93%', yAxis: value },
        ]),
      };
    }

    map(results, (value, i) => {
      const { axisIndex, name, tag, unit } = value;
      (tag || name) && legendData.push({ name: tag || name });
      const yAxisIndex = axisIndex || 0;
      const areaColor = areaColors[i];
      series.push({
        type: value.chartType || 'line',
        name: value.tag || seriseName || value.name || value.key,
        yAxisIndex,
        data: !isBarChangeColor
          ? value.data
          : map(value.data, (item, j) => {
              const sect = Math.ceil(value.data.length / CHANGE_COLORS.length);
              return { ...item, itemStyle: { normal: { color: CHANGE_COLORS[Number.parseInt(j / sect, 10)] } } };
            }),
        label: {
          normal: {
            show: isLabel,
            position: 'top',
            formatter: (label) => label.data.label,
          },
        },
        markLine: i === 0 ? markLine : {},
        connectNulls: true,
        symbol: 'emptyCircle',
        symbolSize: 1,
        barMaxWidth: 50,
        areaStyle: {
          normal: {
            color: noAreaColor ? 'transparent' : areaColor,
          },
        },
      });
      const curMax = value.data ? calMax([value.data]) : [];
      maxArr[yAxisIndex] = maxArr[yAxisIndex] && maxArr[yAxisIndex] > curMax ? maxArr[yAxisIndex] : curMax;
      const curUnitType = value.unitType || ''; // y轴单位
      const curUnit = value.unit || ''; // y轴单位
      yAxis[yAxisIndex] = {
        name: name || yAxisNames[yAxisIndex] || '',
        nameTextStyle: {
          padding: [0, 0, 0, 5],
        },
        position: yAxisIndex === 0 ? 'left' : 'right',
        offset: 10,
        min: 0,
        splitLine: {
          show: true,
        },
        axisTick: {
          show: false,
        },
        axisLine: {
          show: false,
        },
        unitType: curUnitType,
        unit: curUnit,
        axisLabel: {
          margin: 0,
          formatter: (val) => getFormatter(curUnitType, unit).format(val, decimal),
        },
      };
    });

    const formatTime = (timeStr) => moment(Number(timeStr)).format(moreThanOneDay ? 'D/M HH:mm' : 'HH:mm');

    const getTTUnitType = (i) => {
      const curYAxis = yAxis[i] || yAxis[yAxis.length - 1];
      return [curYAxis.unitType, curYAxis.unit];
    };

    const genTTArray = (param) =>
      param.map((unit, i) => {
        return `<span style='color: ${unit.color}'>${cutStr(unit.seriesName, 20)} : ${getFormatter(
          ...getTTUnitType(i),
        ).format(unit.value, 2)}</span><br/>`;
      });

    let defaultTTFormatter = (param) => `${param[0].name}<br/>${genTTArray(param).join('')}`;

    if (time) {
      defaultTTFormatter = (param) => {
        const endTime = time[param[0].dataIndex + 1];
        if (!endTime) {
          return `${formatTime(param[0].name)}<br />${genTTArray(param).join('')}`;
        }
        return `${formatTime(param[0].name)} - ${formatTime(endTime)}<br/>${genTTArray(param).join('')}`;
      };
    }
    const lgFormatter = (name) => {
      const defaultName = legendFormatter ? legendFormatter(name) : name;
      return cutStr(defaultName, 20);
    };

    const haveTwoYAxis = yAxis.length > 1;
    if (haveTwoYAxis) {
      yAxis = yAxis.map((item, i) => {
        // 有数据和无数据的显示有差异
        const hasData = some(results[i].data || [], (_data) => Number(_data) !== 0);
        let { name } = item;
        if (!hasData) {
          name =
            i === 0
              ? `${'  '.repeat(item.name.length + 1)}${item.name}`
              : `${item.name}${'  '.repeat(item.name.length)}`;
        }

        if (i > 1) {
          // 右侧有超过两个Y轴
          yAxis[i].offset = 80 * (i - 1);
        }
        const maxValue = item.max || maxArr[i];
        return { ...item, name, max: maxValue, interval: maxValue / 5 };
        // 如果有双y轴,刻度保持一致
      });
    } else {
      yAxis[0].name = yAxisNames[0] || '';
    }
    const defaultOption = {
      tooltip: {
        trigger: 'axis',
        transitionDuration: 0,
        confine: true,
        axisPointer: {
          type: 'none',
        },
        formatter: tooltipFormatter || defaultTTFormatter,
      },
      legend: {
        bottom: 10,
        padding: [15, 5, 0, 5],
        orient: 'horizontal',
        align: 'left',
        data: legendData,
        formatter: lgFormatter,
        type: 'scroll',
        tooltip: {
          show: true,
          formatter: (t) => cutStr(t.name, 100),
        },
      },
      grid: {
        top: haveTwoYAxis ? 30 : 25,
        left: 15,
        right: haveTwoYAxis ? 30 : 5,
        bottom: 40,
        containLabel: true,
      },
      xAxis: [
        {
          type: 'category',
          data: xAxis || time || [] /* X轴数据 */,
          axisTick: {
            show: false /* 坐标刻度 */,
          },
          axisLine: {
            show: false,
          },
          axisLabel: {
            formatter: xAxis
              ? (value) => value
              : (value) => moment(Number(value)).format(moreThanOneDay ? 'D/M HH:mm' : 'HH:mm'),
          },
          splitLine: {
            show: false,
          },
        },
      ],
      yAxis,
      textStyle: {
        fontFamily: 'arial',
      },
      series,
    };
    return merge(defaultOption, opt);
  };

  const { xAxis, time, results } = data;
  let hasData = size(results) > 0 && !isEmpty(xAxis || time);
  if (time === undefined && xAxis === undefined) {
    hasData = size(results) > 0;
  }

  const handleChange = (value) => {
    setSelectedGroup(value);
  };

  return (
    <>
      <IF check={hasData}>
        <div className="chart-selecter">
          {i18n.t('msp:select instance')}:
          <Select className="my-3" value={selectedGroup || groups[0]} style={{ width: 200 }} onChange={handleChange}>
            {map(groups, (item) => (
              <Option value={item} key={item}>
                {item}
              </Option>
            ))}
          </Select>
        </div>
      </IF>
      <ChartRender {...props} hasData={hasData} getOption={getOption} />
    </>
  );
}
Example #28
Source File: tooltip.ts    From fe-v5 with Apache License 2.0 4 votes vote down vote up
getNearestPoints(eventPosition: EventPosition, xScales: XScales, yScales: YScales, cbk: (nearestPoints: Point[]) => void) {
    const {
      series = [],
      chart: { id, colors },
      tooltip: { shared },
      xkey,
      ykey,
      y0key,
      timestamp,
      fillNull,
    } = this.options;
    const x = xScales.invert(eventPosition.offsetX);
    let nearestPoints: Point[] = [];
    const message: WorkerPostMessage = {
      x,
      xkey,
      ykey,
      y0key,
      timestamp,
      fillNull,
    };

    if (!this.flag) {
      message.id = id;
      message.str = JSON.stringify(series);
      message.flag = this.flag;
      worker.postMessage(message);
      this.flag = true;
    } else {
      message.id = id;
      message.flag = this.flag;
      worker.postMessage(message);
    }

    worker.onmessage = (event: any) => {
      if (this.isMouserover === false) return;

      if (Object.prototype.toString.call(event.data) === '[object Array]') {
        nearestPoints = event.data.map((item: Point) => {
          return {
            ...item,
            x: xScales(item.timestamp),
            y: yScales(item.value),
            color: item.color || getColor(colors, item.serieIndex),
          };
        });
      }
      // 不同时间点数据,还需要再处理一遍
      const nearestPointsGroup = groupBy(nearestPoints, 'x');

      if (keys(nearestPointsGroup).length > 1) {
        let minDistance = Number.POSITIVE_INFINITY;

        // eslint-disable-next-line no-restricted-syntax
        for (const key in nearestPointsGroup) {
          if (Object.prototype.hasOwnProperty.call(nearestPointsGroup, key)) {
            const d = distanceBetweenPointsX(eventPosition.offsetX, Number(key));

            if (d < minDistance) {
              minDistance = d;
              nearestPoints = nearestPointsGroup[key];
            }
          }
        }
      }

      if (!shared) {
        let minDistance = Number.POSITIVE_INFINITY;

        nearestPoints.forEach((point) => {
          const d = distanceBetweenPoints(
            {
              x: eventPosition.offsetX,
              y: eventPosition.offsetY,
            },
            point,
          );

          if (d < minDistance) {
            minDistance = d;
            nearestPoints = [point];
          }
        });
      }

      cbk(nearestPoints);
    };
  }
Example #29
Source File: rds.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
RDS = () => {
  const RDSList = cloudServiceStore.useStore((s) => s.RDSList);
  const { addRDS, getRDSList } = cloudServiceStore.effects;
  const [loading] = useLoading(cloudServiceStore, ['getRDSList']);
  const { getVpcList } = networksStore.effects;
  const { getCloudRegion } = cloudCommonStore;
  const { clearVpcList } = networksStore.reducers;
  const vpcList = networksStore.useStore((s) => s.vpcList);
  const [regions, cloudAccountExist] = cloudCommonStore.useStore((s) => [s.regions, s.cloudAccountExist]);

  useEffectOnce(() => {
    getCloudRegion();
    return () => {
      clearVpcList();
    };
  });

  const [
    { chargeType, chosenRegion, ifSetTagFormVisible, stateChangeKey, selectedList, ifSelected, recordID },
    updater,
    update,
  ] = useUpdate({
    chargeType: 'PostPaid',
    chosenRegion: undefined as string | undefined,
    ifSetTagFormVisible: false,
    stateChangeKey: 1,
    selectedList: [],
    ifSelected: false,
    recordID: '',
  });

  React.useEffect(() => {
    chosenRegion && getVpcList({ region: chosenRegion });
  }, [chosenRegion, getVpcList]);

  const checkSelect = (selectedRows: CLOUD_SERVICE.IRDS[]) => {
    const newIfSelected = !!selectedRows.length;
    const newSelectedList = selectedRows.map(
      (item: CLOUD_SERVICE.IRDS) =>
        ({
          region: item.region,
          vendor: 'alicloud',
          resourceID: item.id,
          oldTags: Object.keys(item.tag),
        } as CLOUD.TagItem),
    );

    update({
      selectedList: newSelectedList,
      ifSelected: newIfSelected,
    });
    return newIfSelected;
  };
  const handleSelect = (selectedRowKeys: string, selectedRows: CLOUD_SERVICE.IRDS[]) => {
    checkSelect(selectedRows);
  };

  const getColumns = () => {
    const columns = [
      getCloudResourceIDNameCol('id', 'name', (id: string, record: CLOUD_SERVICE.IRDS) => {
        if (skipInfoStatusMap.rds.includes(record.status)) {
          goTo(`./${id}/info?region=${record.region || ''}`);
        } else {
          notification.warning({
            message: i18n.t('warning'),
            description: i18n.t('cmp:Instance is not ready'),
          });
        }
      }),
      {
        title: i18n.t('specification'),
        dataIndex: 'spec',
        render: (val: string) => (
          <Tooltip title={val}>
            {get(
              find(specList, (item) => item.value === val),
              'specName',
            ) || val}
          </Tooltip>
        ),
      },
      {
        title: i18n.t('Version'),
        dataIndex: 'version',
        width: 80,
      },
      getCloudResourceStatusCol('rds'),
      {
        title: i18n.t('tag'),
        dataIndex: 'tag',
        align: 'left',
        render: (value: Obj) => {
          const keyArray = keys(value);
          return (
            <TagsRow
              labels={keyArray.map((key) => {
                const label = get(key.split('/'), 1, '');
                return { label, color: customTagColor[label] };
              })}
            />
          );
        },
      },
      getCloudResourceRegionCol('region'),
      getCloudResourceChargeTypeCol('chargeType', 'createTime'),
    ];
    return columns;
  };

  const getFieldsList = (form: FormInstance) => {
    const fieldsList = [
      {
        label: i18n.t('Region'),
        name: 'region',
        type: 'select',
        options: map(regions, ({ regionID, localName }) => ({ value: regionID, name: `${localName} (${regionID})` })),
        itemProps: {
          onChange: (val: string) => {
            form.setFieldsValue({ vpcID: undefined });
            updater.chosenRegion(val);
          },
        },
      },
      {
        label: 'VPC',
        name: 'vpcID',
        type: 'select',
        options: map(vpcList, (item) => ({ value: item.vpcID, name: `${item.vpcName} (${item.vpcID})` })),
      },
      ...MysqlFieldsConfig.getFields(form, {
        chargeType,
        onChangeChargeType: (val) => updater.chargeType(val),
      }),
    ];
    return fieldsList;
  };

  const handleFormSubmit = (data: CLOUD_SERVICE.IRDSCreateBody) => {
    const reData = { ...data, storageSize: +data.storageSize, source: 'resource' };
    const res = addRDS(reData);
    if (isPromise(res)) {
      res.then((addRes: any) => {
        updater.recordID(get(addRes, 'data.recordID'));
      });
    }
  };

  const resetTable = () => {
    updater.stateChangeKey(stateChangeKey + 1);
    checkSelect([]);
  };

  const afterTagFormSubmit = () => {
    resetTable();
  };

  const operationButtons = [
    {
      name: `${i18n.t('Set Labels')}`,
      cb: () => updater.ifSetTagFormVisible(true),
      ifDisabled: false,
    },
  ];

  const menu = (
    <Menu>
      {operationButtons.map((button) => (
        <Menu.Item disabled={button.ifDisabled} key={button.name} onClick={button.cb}>
          {button.name}
        </Menu.Item>
      ))}
    </Menu>
  );

  const extraOperation = () => (
    <Dropdown disabled={!ifSelected} overlay={menu}>
      <Button type="primary">
        <div className="flex">
          {i18n.t('batch setting')}
          <ErdaIcon type="caret-down" className="ml-1" size="20" />
        </div>
      </Button>
    </Dropdown>
  );

  return (
    <>
      <CRUDTable<CLOUD_SERVICE.IRDS>
        key={stateChangeKey}
        isFetching={loading}
        getList={getRDSList}
        name="RDS"
        list={RDSList}
        rowKey="id"
        showTopAdd
        extraOperation={extraOperation}
        getColumns={getColumns}
        getFieldsList={getFieldsList}
        handleFormSubmit={handleFormSubmit}
        addAuthTooltipTitle={addAuthTooltipTitle}
        hasAddAuth={cloudAccountExist}
        onModalClose={() => {
          update({
            chargeType: 'PostPaid',
            chosenRegion: undefined,
          });
        }}
        tableProps={{
          rowSelection: {
            onChange: handleSelect,
          },
        }}
      />
      <Copy selector=".cursor-copy" />
      <SetTagForm
        visible={ifSetTagFormVisible}
        items={selectedList}
        onCancel={() => updater.ifSetTagFormVisible(false)}
        resourceType="RDS"
        showProjectLabel
        showClustertLabel={false}
        afterSubmit={afterTagFormSubmit}
      />
      <ClusterLog recordID={recordID} onClose={() => updater.recordID('')} />
    </>
  );
}