lodash#isEqualWith TypeScript Examples

The following examples show how to use lodash#isEqualWith. 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: dice-yaml-canvas.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
shouldComponentUpdate(nextProps: Readonly<IProps>, nextState: Readonly<any>): boolean {
    if (
      !isEqualWith(nextProps.dataSource, this.props.dataSource, isEqualCustomizer) ||
      nextProps.scale !== this.props.scale ||
      !isEqualWith(nextState, this.state)
    ) {
      return true;
    }

    return false;
  }
Example #2
Source File: dice-yaml-canvas.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
UNSAFE_componentWillReceiveProps(nextProps: Readonly<IProps>): void {
    if (!isEqualWith(nextProps.movingPoints, this.props.movingPoints, isEqualCustomizer)) {
      this.getDeletedMovingPoints(nextProps.movingPoints, this.props.movingPoints);
    }

    if (
      !isEqualWith(nextProps.dataSource, this.props.dataSource, isEqualCustomizer) ||
      nextProps.scale !== this.props.scale
    ) {
      this.loadData(nextProps);
    }
  }
Example #3
Source File: dice-yaml-editor.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
async UNSAFE_componentWillReceiveProps(nextProps: Readonly<IDiceYamlEditorProps>) {
    const isDataSourceChanged = !isEqualWith(nextProps.dataSource, this.props.dataSource, isEqualCustomizer);

    if (isDataSourceChanged || !nextProps.isSelectedItem) {
      this.loadData(nextProps);
    }
  }
Example #4
Source File: build.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
Build = (props: IProps) => {
  const [pipelineDetail, comboPipelines] = buildStore.useStore((s) => [s.pipelineDetail, s.comboPipelines]);
  const { getPipelineDetail, getComboPipelines, getExecuteRecords } = buildStore.effects;
  const { clearComboPipelines, clearPipelineDetail } = buildStore.reducers;
  const params = routeInfoStore.useStore((s) => s.params);
  const [getComboPipelinesLoading, addPipelineLoading, batchCreateTaskLoading] = useLoading(buildStore, [
    'getComboPipelines',
    'addPipeline',
    'batchCreateTask',
  ]);
  const {
    setup: { categoryTitle, type, iconType, addTitle },
    goToDetailLink,
    renderCreateModal,
    showModal,
  } = props;
  const keyProp = type === 'dataTask' ? 'ymlName' : 'branch';

  const reducer = (state: IState, action: { type: string; data: any }): IState => {
    switch (action.type) {
      case 'changeCategory': {
        let filteredPipelines = comboPipelines;
        if (action.data) {
          filteredPipelines = comboPipelines.filter((data) => {
            const { source, ymlName } = data;
            const keyValue = data[keyProp];
            return `${keyValue}-${source}-${ymlName}` === action.data;
          });
        }
        return {
          ...state,
          chosenCategory: action.data,
          pipelines: filteredPipelines,
          activeItem: extractData(filteredPipelines[0]),
        };
      }
      case 'comboPipelinesUpdate': {
        return {
          ...state,
          pipelines: comboPipelines,
          categoryOptions: comboPipelines.map((data: any) => {
            const { source, ymlName } = data;
            const keyValue = data[keyProp];
            return {
              label: `${keyValue}${
                source !== 'dice'
                  ? source === 'qa'
                    ? `(${i18n.t('dop:code quality analysis')}: ${ymlName})`
                    : `(${source}: ${ymlName})`
                  : `(${ymlName})`
              }`,
              value: `${keyValue}-${source}-${ymlName}`,
            };
          }),
          activeItem: params.pipelineID ? state.activeItem : extractData(comboPipelines[0]),
        };
      }
      case 'switchPipeline': {
        const { activeItem: newActiveItem } = action.data;
        if (!newActiveItem || isEqual(state.activeItem, newActiveItem)) {
          return state;
        }
        return { ...state, activeItem: newActiveItem };
      }
      default:
        return state;
    }
  };

  const [states, dispatch] = React.useReducer(reducer, initialState);
  const { chosenCategory, activeItem, pipelines, categoryOptions } = states;

  useUnmount(() => {
    clearComboPipelines();
    clearPipelineDetail();
  });

  React.useEffect(() => {
    getComboPipelines();
  }, [getComboPipelines]);

  React.useEffect(() => {
    const { pipelineID } = params;
    if (pipelineID) {
      const id = toNumber(pipelineID);
      getPipelineDetail({ pipelineID: id }).then((res) => {
        if (res) {
          const detailType = { ...extractData(res), workspace: res.extra.diceWorkspace };
          if (!isEqual(activeItem, detailType)) {
            // when add new for different branch / refresh
            dispatch({ type: 'switchPipeline', data: { activeItem: detailType } });
          }
        }
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [getPipelineDetail, params]); // when switch pipeline or create new

  const isTargetComboPipeline = React.useCallback(
    ({ branch, pagingYmlNames, source, workspace }: any) => {
      return (
        activeItem &&
        isEqualWith(
          { branch, source, workspace },
          activeItem,
          (v1, v2) => v1.source === v2.source && v1.branch === v2.branch && v1.workspace === v2.workspace,
        ) &&
        pagingYmlNames.includes(activeItem.ymlName)
      );
    },
    [activeItem],
  );

  const getExecuteRecordsByPageNo = React.useCallback(
    ({ pageNo }: { pageNo: number }) => {
      if (isEmpty(comboPipelines)) {
        return;
      }
      const targetCombo = comboPipelines.find(isTargetComboPipeline);
      if (targetCombo && activeItem) {
        getExecuteRecords({ ...activeItem, pagingYmlNames: targetCombo.pagingYmlNames, pageNo });
      }
    },
    [activeItem, comboPipelines, getExecuteRecords, isTargetComboPipeline],
  );

  useUpdateEffect(() => {
    activeItem && !isEmpty(comboPipelines) && getExecuteRecordsByPageNo({ pageNo: 1 });
  }, [activeItem, comboPipelines]);

  useUpdateEffect(() => {
    if (activeItem && !isEqual(extractData(pipelineDetail), activeItem)) {
      const target = pipelines.find((p) => isEqual(extractData(p), activeItem));
      target && goToDetailLink({ pipelineID: target.pipelineID }, isEmpty(pipelineDetail));
    }
  }, [activeItem]);

  useUpdateEffect(() => {
    // update pipeline list and select source according to comboPipelines
    comboPipelines && dispatch({ type: 'comboPipelinesUpdate', data: null });
  }, [comboPipelines, dispatch]);

  const renderBuildStatus = ({
    status: inputStatus,
    cancelUser,
  }: {
    status: string;
    cancelUser: { name?: string };
  }) => {
    const definedStatus = statusMap.find((s) => s.status === inputStatus);
    if (definedStatus) {
      const { jumping, colorClass } = definedStatus;
      let { msg } = definedStatus;
      if (inputStatus === 'StopByUser') {
        const { name = '' } = cancelUser || {};
        msg = i18n.t('user {name} canceled', { name });
      }
      const statusStyle = `flow-${colorClass} ${jumping ? 'jumping' : ''}`;
      return (
        <Tooltip title={msg}>
          <div className={`${statusStyle} status-icon`} />
        </Tooltip>
      );
    }
    return null;
  };

  const renderList = () => {
    return (
      <List
        itemLayout="horizontal"
        dataSource={pipelines}
        renderItem={({
          pipelineID,
          commit,
          status,
          cancelUser,
          timeCreated,
          branch,
          ymlName,
          pagingYmlNames,
          source,
          triggerMode,
          workspace,
        }: BUILD.IComboPipeline) => {
          const limitedCommitId = commit ? commit.slice(0, 6) : '';
          const isDisable = disableStatus.includes(status);
          const liProps: any = {};
          const isBigData = source === 'bigdata';
          const displayName = isBigData ? ymlName.slice(ymlName.lastIndexOf('/') + 1) : branch;
          const toolTipName = isBigData ? ymlName : branch;
          if (!isDisable) {
            liProps.onClick = () =>
              dispatch({ type: 'switchPipeline', data: { activeItem: { branch, source, ymlName, workspace } } });
          }

          const cls = classnames({
            'build-item': true,
            'build-item-yml': !isBigData,
            disable: isDisable,
            active: !isDisable && isTargetComboPipeline({ branch, source, pagingYmlNames, workspace }),
          });
          return (
            <div key={pipelineID} className={cls} {...liProps}>
              <div className="w-full flex flex-col justify-between items-center">
                <div className="title flex justify-between items-center">
                  <Tooltip title={toolTipName} overlayClassName="commit-tip">
                    <span className="branch-name font-medium nowrap">
                      <CustomIcon type={iconType} />
                      <span className="nowrap">{displayName}</span>
                    </span>
                  </Tooltip>
                  {!isBigData && renderBuildStatus({ status, cancelUser })}
                </div>
                <IF check={!isBigData}>
                  <div className="yml-name nowrap flex justify-between items-center">
                    <Tooltip title={ymlName} overlayClassName="commit-tip">
                      <span className="name nowrap">
                        <CustomIcon type="wj" />
                        <span className="nowrap">{ymlName}</span>
                      </span>
                    </Tooltip>
                    <div className="workspace">{workspace}</div>
                  </div>
                </IF>
                <div className="item-footer">
                  {!isBigData && (
                    <span>
                      <CustomIcon type="commit" />
                      <span>{limitedCommitId || ''}</span>
                    </span>
                  )}
                  <span className="time">
                    {fromNow(timeCreated)}
                    {triggerMode === 'cron' && <CustomIcon type="clock" />}
                  </span>
                  {isBigData && renderBuildStatus({ status, cancelUser })}
                </div>
              </div>
            </div>
          );
        }}
      />
    );
  };

  const renderLeftSection = () => {
    return (
      <Spin spinning={getComboPipelinesLoading || addPipelineLoading || batchCreateTaskLoading}>
        <div className="build-list-wrap">
          <div className="mr-8 mb-3 ml-3">
            <Select
              showSearch
              className="w-full"
              optionFilterProp="children"
              value={chosenCategory}
              onChange={(e: any) => dispatch({ type: 'changeCategory', data: e })}
              dropdownClassName="branch-select"
              filterOption={(input: any, option: any) => {
                return get(option, 'props.title').toLowerCase().indexOf(input.toLowerCase()) >= 0;
              }}
            >
              <Option value="" title="">
                {categoryTitle}
              </Option>
              {categoryOptions.map(({ label, value }: { label: string; value: string }) =>
                type === 'dataTask' ? (
                  <Option key={value} title={label} value={value}>
                    {label.slice(label.lastIndexOf('/') + 1)}
                  </Option>
                ) : (
                  <Option key={value} title={label} value={value}>
                    {label}
                  </Option>
                ),
              )}
            </Select>
          </div>
          <div className="build-list">{renderList()}</div>
        </div>
      </Spin>
    );
  };

  const getList = (pipelineID: number, buildDetailItem: BUILD.IActiveItem, isRerun: boolean) => {
    getComboPipelines();
    if (isEqual(activeItem, buildDetailItem)) {
      if (isRerun) {
        goToDetailLink({ pipelineID });
      } else {
        getPipelineDetail({ pipelineID });
      }
      getExecuteRecordsByPageNo({ pageNo: 1 });
    }
  };

  const renderRightSection = () => (
    <BuildDetail
      getPipelines={getList}
      activeItem={activeItem}
      goToDetailLink={goToDetailLink}
      getExecuteRecordsByPageNo={getExecuteRecordsByPageNo}
    />
  );

  return (
    <SplitPage className="runtime-build-main">
      <SplitPage.Left width={300} className="pr-0 spin-h-full">
        {renderLeftSection()}
      </SplitPage.Left>
      <SplitPage.Right pl32>{renderRightSection()}</SplitPage.Right>
      <TopButtonGroup>
        <Button type="primary" onClick={showModal}>
          {addTitle}
        </Button>
      </TopButtonGroup>
      {renderCreateModal()}
    </SplitPage>
  );
}
Example #5
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,
        }}
      />
    </>
  );
}