lodash#isPlainObject TypeScript Examples

The following examples show how to use lodash#isPlainObject. 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 gant-design with MIT License 7 votes vote down vote up
isEqualObj = (obj, obj2) => {
  let _EqualObj = true;
  if (!isPlainObject(obj) || !isPlainObject(obj2)) {
    if (isEmptyObj(obj) && isEmptyObj(obj2)) return true;
    return isEqual(obj, obj2);
  }
  if (isNull(obj) && isNull(obj2)) return true;
  if (isNull(obj) || isNull(obj2)) {
    return false;
  }
  const newObj = { ...obj, ...obj2 };
  for (let i in newObj) {
    let value1 = get(obj, i),
      value2 = get(obj2, i);
    if (
      typeof value1 === 'object' &&
      typeof value2 === 'object' &&
      !Array.isArray(value1) &&
      !Array.isArray(value2)
    ) {
      _EqualObj = isEqualObj(value1, value2);
    } else {
      if (!(isEmptyObj(value1) && isEmptyObj(value2))) {
        value2 = typeof value2 == 'number' ? value2 : value2 + '';
        value1 = typeof value1 == 'number' ? value1 : value1 + '';
        _EqualObj = isEqual(value2, value1);
      }
    }
    if (!_EqualObj) {
      return _EqualObj;
    }
  }
  return true;
}
Example #2
Source File: request.ts    From generator-earth with MIT License 6 votes vote down vote up
export function getUrlWithParamsAndUuid(url: string, params?: IAnyObject): string {
    if (!params || !isPlainObject(params)) params = {}

    let conn: string

    if (url.indexOf('?') === -1) {
        conn = '?'
    } else if (/\?$/.test(url)) {
        conn = ''
    } else {
        conn = '&'
    }

    params.reqid = uuidv4()
    return `${url}${conn}${stringifyParams(params)}`
}
Example #3
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
ReadonlyForm = ({ fieldsList, data }: IReadonlyProps) => {
  const readonlyView: JSX.Element[] = [];
  forEach(fieldsList, (item, index) => {
    const { itemProps, label, name, viewType, getComp } = item;
    if (!(itemProps && itemProps.type === 'hidden') && label) {
      const value = name === undefined && getComp ? getComp() : get(data, name || '');
      if (value !== undefined && value !== null && value !== '') {
        if (viewType === 'image') {
          readonlyView.push(
            <Form.Item label={label} key={index}>
              <Avatar shape="square" src={value} size={100} />
            </Form.Item>,
          );
        } else {
          readonlyView.push(
            <Form.Item label={label} key={index}>
              <p style={{ wordBreak: 'break-all' }}>
                {Array.isArray(value) ? (
                  map(value, (v: string, i: number) => (
                    <span key={`${i}${v}`}>
                      &nbsp;&nbsp;{isPlainObject(v) ? JSON.stringify(v) : v.toString()}
                      <br />
                    </span>
                  ))
                ) : (
                  <>&nbsp;&nbsp;{value !== undefined ? value.toString() : undefined}</>
                )}
              </p>
            </Form.Item>,
          );
        }
      }
    }
  });
  return <Form layout="vertical">{readonlyView}</Form>;
}
Example #4
Source File: readonly-field.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
ReadonlyField = ({ renderData, style, className, value }: IProps) => {
  const realData = React.useMemo(() => {
    return renderData
      ? isFunction(renderData)
        ? renderData(value)
        : renderData
      : isPlainObject(value)
      ? JSON.stringify(value)
      : value
      ? value.toString()
      : '-';
  }, [renderData, value]);

  return (
    <div style={{ overflow: 'auto', ...style }} className={className}>
      {realData}
    </div>
  );
}
Example #5
Source File: test-env-detail.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
transObjToList = (obj: object) => {
  if (!isPlainObject(obj)) {
    return obj;
  }
  const list: Obj[] = [];
  map(obj, (v: string, k: string) => {
    list.push({ key: k, value: v });
  });
  return list;
}
Example #6
Source File: paramsSerializer.ts    From linkedin-private-api with MIT License 6 votes vote down vote up
paramsSerializer = (params: Record<string, string | Record<string, string>>): string => {
  const encodedParams = mapValues(params, value => {
    if (!isArray(value) && !isPlainObject(value)) {
      return value.toString();
    }

    if (isArray(value)) {
      return `List(${value.join(',')})`;
    }

    const encodedList = reduce(
      value as Record<string, string>,
      (res, filterVal, filterKey) => `${res}${res ? ',' : ''}${encodeFilter(filterVal, filterKey)}`,
      '',
    );

    return `List(${encodedList})`;
  });

  return stringify(encodedParams, undefined, undefined, {
    encodeURIComponent: uri => uri,
  });
}
Example #7
Source File: test-env-detail.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
transGlobal = (list: any[]) => {
  if (!Array.isArray(list)) {
    return list;
  }
  const dataTemp = { type: '', value: '', desc: '' };
  const obj = {};
  map(list, (item) => {
    const { key, value, ...rest } = item || {};
    if (key) {
      let reItem = {};
      if (isPlainObject(value)) {
        reItem = { ...dataTemp, ...value, ...rest };
      } else {
        reItem = {
          ...dataTemp,
          value,
          ...rest,
        };
      }
      if (!reItem.type) reItem.type = typeMap.auto.string;
      obj[key] = reItem;
    }
  });
  return obj;
}
Example #8
Source File: helper.ts    From brick-design with MIT License 6 votes vote down vote up
export function setVariable(
  data: { [propName: string]: any },
  key: string,
  value: any,
) {
  data = data || {};

  if (key in data) {
    data[key] = value;
    return;
  }

  const parts = keyToPath(key);
  const last = parts.pop() as string;

  while (parts.length) {
    const key = parts.shift() as string;
    if (isPlainObject(data[key])) {
      data = data[key] = {
        ...data[key],
      };
    } else if (Array.isArray(data[key])) {
      data[key] = data[key].concat();
      data = data[key];
    } else if (data[key]) {
      // throw new Error(`目标路径不是纯对象,不能覆盖`);
      // 强行转成对象
      data[key] = {};
      data = data[key];
    } else {
      data[key] = {};
      data = data[key];
    }
  }

  data[last] = value;
}
Example #9
Source File: handleStateFields.ts    From brick-design with MIT License 6 votes vote down vote up
export function getStateFields(data: any, fieldsRet?: string[]) {
  const ret = fieldsRet || [];
  if (Array.isArray(data)) {
    return data.map((item) => getStateFields(item));
  } else if (!data) {
    return ret;
  }
  each(data, (value, key) => {
    if (value === '$$' || value === '&') {
      ret.push(ALL_PROPS);
    } else if (key !== '$' && key.includes('$')) {
      //todo 字符串方法体
      resolveFieldFromExpression(value, ret);
    } else if (
      typeof value === 'string' &&
      /(\\)?\$(?:([a-z0-9_\.]+|&|\$)|{([^}{]+?)})/gi.test(value)
    ) {
      value.replace(
        /(\\)?\$(?:([a-z0-9_\.]+|&|\$)|{([^}{]+?)})/gi,
        (_, escape) => {
          !escape && resolveVariableAndFilterField(_, ret);
          return '';
        },
      );
    } else if (isPlainObject(value)) {
      getStateFields(value, ret);
    }
  });

  return [...new Set(ret.filter((field) => field !== 'funParams'))];
}
Example #10
Source File: traverse.ts    From S2 with MIT License 6 votes vote down vote up
traverse = <T>(value: T, seen?: Set<T>) => {
  // eslint-disable-next-line dot-notation
  if (!isObject(value) || (value as any)['__v_skip']) {
    return value;
  }

  if (!isProxy(value) && !isRef(value)) {
    return value;
  }

  seen = seen || new Set();
  if (seen.has(value)) {
    return value;
  }
  seen.add(value);
  if (isRef(value)) {
    traverse(value.value, seen);
  } else if (isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      traverse(value[i], seen);
    }
  } else if (isSet(value) || isMap(value)) {
    value.forEach((v: any) => {
      traverse(v, seen);
    });
  } else if (isPlainObject(value)) {
    for (const key in value as any) {
      traverse((value as any)[key], seen);
    }
  }
  return value;
}
Example #11
Source File: compile.ts    From fect with MIT License 6 votes vote down vote up
compile = async () => {
  setNodeENV('production')
  const { userConfig, path: configPath } = await resolveConfig()
  const { lib } = userConfig
  if (!isPlainObject(lib)) {
    return logErr(`[Non Error!] you can not use it when your un set library in your config at ${configPath}`)
  }

  const { format, name, input } = lib

  const components = fs
    .readdirSync(input)
    .filter((v) => v !== 'utils')
    .map((_) => {
      _ = _.replace(/\-(\w)/g, (_, k: string) => k.toUpperCase())
      _ = _.charAt(0).toUpperCase() + _.slice(1)
      return _
    })

  await runTask('CommonJs', () => cjsTask(input, components))
  await runTask('EsModule', () => esmTask(input, components))
  if (format === 'umd' || format.includes('umd')) {
    await runTask('UMD', () => umdTask(input, name))
  }
  await runTask('Declaration', () => declarationTask(input))
}
Example #12
Source File: model-test-utils.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
export function isObject(value: unknown): value is Record<string, unknown> {
  return isPlainObject(value);
}
Example #13
Source File: validate.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
export function isLibraryFile(
  fileName: string,
  fileContent: Json,
  strict: boolean,
  printValidationErrors: boolean
): fileContent is SchemaForApiJsonFiles {
  const valid = validate(fileContent);

  // Only printing when requested because the output is very verbose so it should only used for debugging.
  /* istanbul ignore if */
  if (!valid && printValidationErrors) {
    console.log(JSON.stringify(validate.errors, undefined, 2));
  }

  if (!strict) {
    if (!valid) {
      // We only return an error in non-strict mode if symbols is not an array
      return (
        isPlainObject(fileContent) &&
        isArray((fileContent as Record<string, unknown>).symbols)
      );
    }
    return true;
  }
  return valid as boolean;
}
Example #14
Source File: compose.ts    From gant-design with MIT License 6 votes vote down vote up
objectToPath = (obj: object): Array<string> => {
    const paths: Array<string> = []
    const inner = (obj: object, parentKey = ''): void => {
        Object.keys(obj).forEach(k => {
            const combineKey = parentKey ? parentKey + '.' + k : k
            const value = obj[k]
            if (value && isPlainObject(value) && !moment.isMoment(value)) {
                inner(value, combineKey)
            } else {
                paths.push(combineKey)
            }
        })
    }
    inner(obj)
    return paths
}
Example #15
Source File: is-mail-server-config.ts    From js-client with MIT License 6 votes vote down vote up
isMailServerConfig = (v: any): v is MailServerConfig => {
	if (isPlainObject(v)) {
		return (
			isOfTypeOrNil(v.server, isString) &&
			isOfTypeOrNil(v.password, isString) &&
			isOfTypeOrNil(v.string, isString) &&
			isOfTypeOrNil(v.port, isNumber) &&
			isOfTypeOrNil(v.useTLS, isBoolean) &&
			isOfTypeOrNil(v.insecureSkipVerify, isBoolean)
		);
	}
	return false;
}
Example #16
Source File: fetcher.ts    From po8klasie with GNU General Public License v3.0 6 votes vote down vote up
camelCaseKeys = (o: Record<string, unknown>): Record<string, unknown> | unknown[] => {
  if (isPlainObject(o)) {
    const n: Record<string, unknown> = {};

    Object.keys(o).forEach((k) => {
      n[camelCase(k)] = camelCaseKeys(o[k] as Record<string, unknown>);
    });

    return n;
  }
  if (Array.isArray(o)) {
    return o.map((i) => camelCaseKeys(i));
  }

  return o;
}
Example #17
Source File: test-env-detail.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
KVPairTable = (props: any) => {
  const { value, disabled } = props;
  const columns = [
    {
      title: i18n.t('Name'),
      dataIndex: 'Key',
      width: 200,
    },
    {
      title: i18n.t('dop:Parameter content'),
      dataIndex: 'Value',
    },
    {
      title: i18n.t('Operations'),
      key: 'op',
      width: 80,
      render: (_: any, record: any) => (record.isLast ? null : record.Op),
    },
  ];

  const _value: any = map(transObjToList(value), (item) => {
    const newItem = {};
    forEach(item, (v, k) => {
      if (isPlainObject(v)) {
        forEach(v as Obj, (subV: string, subK: string) => {
          newItem[subK] = subV;
        });
      } else {
        newItem[k] = v;
      }
    });
    return newItem;
  });

  if (props.KeyDescComp) {
    columns.splice(2, 0, {
      title: i18n.t('Description'),
      dataIndex: 'KeyDescComp',
      width: 150,
    });
  }
  if (props.DescComp) {
    columns.splice(1, 0, {
      title: i18n.t('dop:Parameter type'),
      dataIndex: 'Desc',
      width: 150,
    });
  }

  return (
    <KVPair {...props} value={_value} emptyHolder={!disabled} autoAppend={!disabled} compProps={{ disabled }}>
      {({ CompList }: any) => {
        const data = CompList.map((d: any, index: number) => ({
          index,
          ...CompList[index],
          isLast: index === CompList.length - 1,
        }));
        return <Table rowKey={'index'} columns={columns} pagination={false} dataSource={data} scroll={{ x: '100%' }} />;
      }}
    </KVPair>
  );
}
Example #18
Source File: request.ts    From generator-earth with MIT License 5 votes vote down vote up
export function stringifyParams(params?: IAnyObject): string {
    if (!isPlainObject(params)) params = {}
    const _params = formatParams(params, (value) => (typeof value === 'object' ? JSON.stringify(value) : value))
    return Object.keys(_params).map((key) => (key + '=' + encodeURIComponent(_params[key]))).join('&')
}
Example #19
Source File: combiner.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
export function createCombiner<P, O>({
  CombinerItem,
  valueFixIn = defaultFix,
  valueFixOut = defaultFix,
  defaultItem,
}: IProps<P, O>) {
  return (props: ICompProps<P, O>) => {
    const { value, onChange, disabled, ...rest } = props;
    const changeData = (val: any) => {
      onChange(valueFixOut(cloneDeep(val)));
    };

    const updateItem = (d: any, index: number) => {
      const newVal = map(valueFixIn(value), (val, idx) => {
        if (isPlainObject(val)) {
          if (index === idx) {
            const curVal = produce(val, (draft: Obj) => {
              const curKey = Object.keys(d)[0];
              set(draft, curKey, d[curKey]);
            });
            return curVal;
          }
          return val;
        } else {
          return index === idx ? d : val;
        }
      });
      changeData(newVal);
    };

    const addItem = () => {
      const _defaultItem = typeof defaultItem === 'function' ? defaultItem(props) : defaultItem;
      changeData([...valueFixIn(value), _defaultItem]);
    };

    const deleteItem = (index: number) => {
      changeData(filter(value, (_, idx) => index !== idx));
    };
    return (
      <div className="dice-form-nusi-combiner-component">
        {map(valueFixIn(value), (item, index) => (
          <CombinerItem
            {...rest}
            disabled={disabled}
            className="combiner-item"
            key={`${index}`}
            data={item}
            updateItem={(d: any) => {
              updateItem(d, index);
            }}
            operation={
              disabled ? (
                <ErdaIcon type="reduce-one" className="combiner-operation not-allowed" />
              ) : (
                <ErdaIcon type="reduce-one" className="combiner-operation" onClick={() => deleteItem(index)} />
              )
            }
          />
        ))}
        {disabled ? (
          <ErdaIcon type="add-one" className="combiner-operation not-allowed" />
        ) : (
          <Tooltip title={i18n.t('common:click to add item')}>
            <ErdaIcon type="add-one" className="combiner-operation" onClick={addItem} />
          </Tooltip>
        )}
      </div>
    );
  };
}
Example #20
Source File: resolve.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
// Exported for testing purpose
export function resolveType({
  model,
  type,
  typeNameFix,
  strict,
}: {
  model: UI5SemanticModel;
  type: UI5Type | string | undefined;
  typeNameFix: TypeNameFix;
  strict: boolean;
}): UI5Type | undefined {
  // String types are unresolved
  if (typeof type === "string") {
    type = {
      kind: "UnresolvedType",
      name: type,
    };
  }

  // Invalid type - this can only happen if the schema validation failed and we're in non-strict mode
  if (!isPlainObject(type)) {
    return undefined;
  }

  // Before resolving all types are UnresolvedType or undefined
  if (type === undefined || type.kind !== "UnresolvedType") {
    return type;
  }

  const typeName = fixTypeName(type.name, typeNameFix);
  if (typeName === undefined) {
    return undefined;
  }

  const typeObj = findValueInMaps<UI5Type>(
    typeName,
    model.classes,
    model.interfaces,
    model.enums,
    model.namespaces,
    model.typedefs
  );
  if (typeObj !== undefined) {
    return typeObj;
  }

  const primitiveTypeName = getPrimitiveTypeName(typeName);
  if (primitiveTypeName !== undefined) {
    return {
      kind: "PrimitiveType",
      name: primitiveTypeName,
    };
  }
  if (typeName.endsWith("[]")) {
    const innerTypeName = typeName.substring(0, typeName.length - "[]".length);
    const innerType = resolveType({
      model,
      type: innerTypeName,
      typeNameFix,
      strict,
    });
    return {
      kind: "ArrayType",
      type: innerType,
    };
  } else {
    error(`Unknown type: ${typeName}`, strict);
    return {
      kind: "UnresolvedType",
      name: typeName,
    };
  }
}
Example #21
Source File: uniform.ts    From GWebGPUEngine with MIT License 5 votes vote down vote up
function extractUniformsRecursively(
  uniformName: string,
  uniformValue: IUniform,
  uniforms: {
    [key: string]: IUniform;
  },
  prefix: string,
) {
  if (
    uniformValue === null ||
    typeof uniformValue === 'number' || // u_A: 1
    typeof uniformValue === 'boolean' || // u_A: false
    (Array.isArray(uniformValue) && typeof uniformValue[0] === 'number') || // u_A: [1, 2, 3]
    isTypedArray(uniformValue) || // u_A: Float32Array
    // @ts-ignore
    uniformValue === '' ||
    // @ts-ignore
    uniformValue.resize !== undefined
  ) {
    uniforms[`${prefix && prefix + '.'}${uniformName}`] = uniformValue;
    return;
  }

  // u_Struct.a.b.c
  if (isPlainObject(uniformValue)) {
    Object.keys(uniformValue).forEach((childName) => {
      extractUniformsRecursively(
        childName,
        // @ts-ignore
        uniformValue[childName],
        uniforms,
        `${prefix && prefix + '.'}${uniformName}`,
      );
    });
  }

  // u_Struct[0].a
  if (Array.isArray(uniformValue)) {
    // @ts-ignore
    uniformValue.forEach((child, idx) => {
      Object.keys(child).forEach((childName) => {
        extractUniformsRecursively(
          childName,
          // @ts-ignore
          child[childName],
          uniforms,
          `${prefix && prefix + '.'}${uniformName}[${idx}]`,
        );
      });
    });
  }
}
Example #22
Source File: pure-edit-list.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
notEmptyValue = (v: any) => {
  if (isPlainObject(v) || isArray(v)) {
    return !isEmpty(v);
  }
  return !(v === '' || v === null || v === undefined);
}
Example #23
Source File: BrickDescriptions.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function BrickDescriptions(
  props: BrickDescriptionsProps
): React.ReactElement {
  const { configProps, itemList, hideGroups } = props;

  const hideGroupsSet = new Set(hideGroups);
  // istanbul ignore next
  const renderLegacyComponent = (
    item: BrickDescriptionsItemProps
  ): React.ReactElement => {
    // eslint-disable-next-line no-console
    console.warn(
      "`<presentational-bricks.brick-descriptions>.itemList[].component` are deprecated, use `useBrick` instead."
    );
    const { field, component } = item;
    if (field && Array.isArray(props.dataSource[field])) {
      return props.dataSource[field].map((data: any, i: number) => (
        <component.brick
          key={`${item.id}-${i}`}
          ref={(el: any) => {
            el &&
              Object.assign(el, {
                item,
                dataSource: data,
                ...component.properties,
              });
          }}
        />
      ));
    } else {
      return (
        <component.brick
          key={item.id}
          ref={(el: any) => {
            el &&
              Object.assign(el, {
                item,
                dataSource: props.dataSource,
                ...component.properties,
              });
          }}
        />
      );
    }
  };

  // istanbul ignore next
  const renderBrick = (
    item: BrickDescriptionsItemProps
  ): React.ReactElement => {
    return (
      <BrickAsComponent useBrick={item.useBrick} data={props.dataSource} />
    );
  };

  return (
    <Descriptions
      title={props.descriptionTitle}
      column={props.column}
      size={props.size}
      bordered={props.bordered}
      layout={props.layout}
      className={styles.descriptionWrapper}
      {...configProps}
    >
      {itemList
        ?.filter((item) => !hideGroupsSet.has(item.group))
        .map((item, idx) => {
          const { text, component, useBrick, ...itemProps } = item;
          return (
            <Descriptions.Item
              key={item.id || idx}
              {...itemProps}
              className={styles.descriptionItem}
            >
              {useBrick
                ? renderBrick(item)
                : component
                ? renderLegacyComponent(item)
                : isPlainObject(text)
                ? JSON.stringify(text)
                : text}
            </Descriptions.Item>
          );
        })}
    </Descriptions>
  );
}
Example #24
Source File: createApp.tsx    From redux-with-domain with MIT License 4 votes vote down vote up
export default function createApp(createOpt: CreateOpt = {}) {
  let app: App
  const { initialReducer, onError } = createOpt

  // internal map for modules
  const _modules: Modules = {}

  let _router = <div />

  const _middleWares: Middleware<{}>[] = [...defaultMiddleWares]
  const sagaMiddleware: SagaMiddleware<{}> = createSagaMiddleware()

  _middleWares.push(sagaMiddleware)

  function hasSubModule(module: Module) {
    let flag = false
    forEach(module, value => {
      if (isModule(value)) {
        flag = true
      }
    })
    return flag
  }

  function _addModule(m: Module) {
    invariant(!_modules[m.namespace], `kop nodule ${m.namespace} exists`)

    if (!isEmpty(m.entities)) {
      forEach(m.entities, e => {
        _addModule(e)
      })
    }
    _modules[m.namespace] = m
  }

  // create reducer
  // http://redux.js.org/docs/recipes/reducers/RefactoringReducersExample.html
  function createReducer(
    initialState = {},
    handlers: ReducerHandler,
    defaultReducer?: DefaultReducer<any>
  ) {
    return (state = initialState, action: Action) => {
      if (
        Object.prototype.hasOwnProperty.call(handlers, action.type) &&
        isFunction(handlers[action.type])
      ) {
        const handler = handlers[action.type]
        return handler(state, action)
      }
      if (defaultReducer && isFunction(defaultReducer)) {
        return defaultReducer(state, action)
      }
      return state
    }
  }

  function loopCombineReducer(
    tree: any,
    combineRootreducer = true,
    parentNode?: string | ParentNode
  ) {
    const childReducer: any = mapValues(tree, node => {
      if (!isModule(node)) {
        return loopCombineReducer(node)
      }

      if (hasSubModule(node)) {
        const subModuleMap = pickBy(node, value => isModule(value))
        return loopCombineReducer(subModuleMap, true, node)
      }

      return createReducer(
        node._initialState,
        node._reducers,
        node._defaultReducer
      )
    })

    let result

    if (isEmpty(parentNode)) {
      result = {
        ...childReducer
      }
    } else if (parentNode === 'root') {
      invariant(
        !initialReducer || isPlainObject(initialReducer),
        'initialReducer should be object'
      )
      const noDuplicatedKeys = !hasDuplicatedKeys(
        initialReducer,
        childReducer,
        'router'
      )
      invariant(
        noDuplicatedKeys,
        'initialReducer has reduplicate keys with other reducers'
      )

      result = {
        ...initialReducer,
        ...childReducer
      }
    } else {
      result = {
        base: createReducer(
          (parentNode as ParentNode)._initialState,
          (parentNode as ParentNode)._reducers,
          (parentNode as ParentNode)._defaultReducer
        ),
        ...childReducer
      }
    }

    if (parentNode === 'root' && !combineRootreducer) return result

    return combineReducers(result)
  }

  function addModule(module: Module | Module[]) {
    if (isArray(module)) {
      module.forEach(m => {
        _addModule(m)
      })
    } else {
      _addModule(module)
    }
  }

  function initInjectModules(presenter: Presenter) {
    forEach(presenter.injectModules, (name: string) => {
      invariant(_modules[name], `please check the kop-module ${name} is added`)
      extend(_modules[name].presenter, {
        loaded: true, // 标记已被装载,在module中会注入 presentor 的 seletor
        selectors: presenter.selectors,
        actions: presenter.actions
      })
    })
  }

  function createRootReducer(combineRootreducer = true) {
    const moduleData = cloneDeepWith(_modules)
    const moduleTree = reduce(
      moduleData,
      (result, value, key) => {
        const module = get(result, toStorePath(key))

        if (isModule(value)) {
          if (module) {
            return set(result, `${toStorePath(key)}.base`, value)
          }
          return set(result, toStorePath(key), value)
        }

        return result
      },
      {}
    )

    return loopCombineReducer(moduleTree, combineRootreducer, 'root')
  }

  function addPage(pageModule: Module, opt: PageOption = {}) {
    const { containers } = opt

    if (containers && containers.length > 0) {
      addModule(containers)
    }

    if (pageModule.injectModules && pageModule.injectModules.length > 0) {
      initInjectModules(pageModule)
    }

    addModule(pageModule)
  }

  function addDomain(module: Module) {
    addModule(module)
  }

  const addGlobal = _addModule

  function removeModule(module: Module | Module[]) {
    const _remove = (m: Module) => {
      invariant(
        _modules[m.namespace],
        `error: the kop-module - ${m.namespace} is not existed`
      )

      // hack redux-devtools's bug
      if (
        m &&
        m.actions &&
        !isPresenter(m) &&
        m.type !== DOMAIN_MODULE &&
        (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      ) {
        _store.dispatch(m.actions.__reset())
      }

      delete _modules[m.namespace]
      _store.dispatch({ type: `${m.namespace}/@@KOP_CANCEL_EFFECTS` })
    }

    if (isArray(module)) {
      module.forEach(m => {
        _remove(m)
      })
      _store.replaceReducer(createRootReducer() as Reducer)
    } else if (module) {
      _remove(module)
      _store.replaceReducer(createRootReducer() as Reducer)
    }
  }

  function addRouter(r: JSX.Element) {
    _router = r
  }

  function addMiddleWare(middleWare: Middleware) {
    const add = (m: Middleware) => {
      _middleWares.push(m)
    }

    add(middleWare)
  }

  function getStore() {
    return _store
  }

  function createAppStore() {
    // inject chrome redux devtools
    let composeEnhancers

    if (
      process.env.NODE_ENV !== 'production' &&
      (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ) {
      composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    } else {
      composeEnhancers = compose
    }

    const enhancer = composeEnhancers(applyMiddleware(..._middleWares))
    const rootReducer = createRootReducer()
    return createStore(rootReducer as Reducer, enhancer)
  }

  function getArguments(...args: any[]) {
    let domSelector: string | null = null
    let callback = noop

    if (args.length === 1) {
      // app.start(false) means jump render phase and return early
      if (args[0] === false) {
        return {
          domSelector: '',
          callback: noop,
          shouldRender: false
        }
      }
      if (isString(args[0])) {
        domSelector = args[0]
      }
      if (isFunction(args[0])) {
        callback = args[0]
      }
    }

    if (args.length === 2) {
      domSelector = args[0]
      callback = args[1]
    }

    return {
      domSelector,
      callback,
      shouldRender: true
    }
  }

  function renderAppElement(
    domSelector: string | null,
    callback: Function,
    shouldRender: boolean
  ) {
    const $elem = <Provider store={_store}>{_router}</Provider>

    // skip render when shouldRender is false
    if (shouldRender) {
      if (isString(domSelector)) {
        render($elem, document.querySelector(domSelector), () => {
          callback()
        })
      } else if (isElement(domSelector)) {
        render($elem, domSelector, () => {
          callback()
        })
      } else {
        callback()
      }
    }

    return $elem
  }

  function _onError(e: Error) {
    if (!onError) {
      console.error(e)
    } else {
      onError(e)
    }
  }

  // create root saga
  function createSaga(module: Module) {
    return function*() {
      if (isArray(module.effects)) {
        for (let i = 0, k = module.effects.length; i < k; i++) {
          const task = yield effects.fork(function*(): any {
            try {
              if (module.effects) {
                yield module.effects[i]()
              }
            } catch (e) {
              _onError(e)
            }
          })

          yield effects.fork(function*() {
            yield effects.take(`${module.namespace}/@@KOP_CANCEL_EFFECTS`)
            yield effects.cancel(task)
          })
        }
      }

      if (isArray(module.watchers)) {
        for (let i = 0, k = module.watchers.length; i < k; i++) {
          const task = yield effects.fork(function*(): any {
            try {
              if (module.watchers) {
                yield module.watchers[i]()
              }
            } catch (e) {
              _onError(e)
            }
          })
          yield effects.fork(function*() {
            yield effects.take(`${module.namespace}/@@KOP_CANCEL_EFFECTS`)
            yield effects.cancel(task)
          })
        }
      }
    }
  }

  function start(...args: any[]) {
    const { domSelector, callback, shouldRender } = getArguments(...args)

    _store = createAppStore()

    initSelectorHelper(_store)

    app._store = _store

    window[KOP_GLOBAL_STORE_REF] = _store

    app._modules = _modules

    forEach(app._modules, (m: Module) => {
      if (m.type === ENTITY_MODULE) {
        m._store = _store // 提供domainModel的遍历action
      }
    })

    app._middleWares = _middleWares

    app._router = _router

    forEach(_modules, module => {
      sagaMiddleware.run(createSaga(module))
    })

    return renderAppElement(domSelector, callback, shouldRender)
  }

  // 集成模式初始化,返回 saga,reducer 等等
  function _init() {
    const rootReducer = createRootReducer(false)
    return {
      rootReducer,
      sagaMiddleware
    }
  }

  // 集成模式启动,由外部 Redux 控制 App 渲染流程
  function _run(store: Store<any, AnyAction>) {
    initSelectorHelper(store)
    app._store = store
    window[KOP_GLOBAL_STORE_REF] = store
    app._modules = _modules
    forEach(app._modules, (m: Module) => {
      if (m.type === ENTITY_MODULE) {
        m._store = store
      }
    })
    app._router = _router
    forEach(_modules, module => {
      sagaMiddleware.run(createSaga(module))
    })
  }

  app = {
    addModule, // register container module
    addPage, // register page module
    addDomain, // register domain module
    addGlobal, // register global module
    addRouter,
    addMiddleWare,
    start,
    getStore,
    removeModule,
    _init,
    _run
  }

  return app
}
Example #25
Source File: ui5-model-spec.ts    From ui5-language-assistant with Apache License 2.0 4 votes vote down vote up
describe("the UI5 language assistant ui5 model", () => {
  // The default timeout is 2000ms and getSemanticModel can take ~3000-5000ms
  const GET_MODEL_TIMEOUT = 10000;
  const VERSION = "1.71.14";
  const NO_CACHE_FOLDER = undefined;

  function assertSemanticModel(ui5Model: UI5SemanticModel): void {
    expect(ui5Model.version).to.equal(VERSION);

    expect(Object.keys(ui5Model.classes).length).to.be.greaterThan(200);
    expect(Object.keys(ui5Model.namespaces).length).to.be.greaterThan(200);
    expect(Object.keys(ui5Model.interfaces).length).to.be.greaterThan(30);
    expect(Object.keys(ui5Model.functions).length).to.be.greaterThan(30);
    expect(Object.keys(ui5Model.enums).length).to.be.greaterThan(200);
    expect(Object.keys(ui5Model.typedefs).length).to.be.greaterThan(10);

    expect(Object.keys(ui5Model.classes)).to.include("sap.m.List");
    expect(Object.keys(ui5Model.namespaces)).to.include("sap.m");
    expect(Object.keys(ui5Model.interfaces)).to.include("sap.f.ICard");
    expect(Object.keys(ui5Model.functions)).to.include(
      "module:sap/base/assert"
    );
    expect(Object.keys(ui5Model.enums)).to.include("sap.m.ButtonType");
    expect(Object.keys(ui5Model.typedefs)).to.include("sap.ui.fl.Selector");

    // Dist layer
    expect(Object.keys(ui5Model.classes)).to.include("sap.ui.vk.Camera");
    expect(Object.keys(ui5Model.namespaces)).to.include("sap.apf.base");
    expect(Object.keys(ui5Model.enums)).to.include(
      "sap.ca.ui.charts.ChartSelectionMode"
    );
  }

  it("will get UI5 semantic model", async () => {
    const ui5Model = await getSemanticModel(NO_CACHE_FOLDER);
    assertSemanticModel(ui5Model);
  }).timeout(GET_MODEL_TIMEOUT);

  it("doesn't fail if a file cannot be fetched", async () => {
    const ui5Model = await getSemanticModelWithFetcher(async (url: string) => {
      return {
        ok: false,
        json: (): never => {
          throw new Error(`Cannot read from ${url}`);
        },
      };
    }, NO_CACHE_FOLDER);
    expect(ui5Model).to.exist;
  });

  describe("with cache", () => {
    describe("cache in temp dir", () => {
      let cachePath: string;
      beforeEach(async () => {
        ({ path: cachePath } = await tempDir());
      });

      afterEach(async () => {
        // The temp folder will contain files at the end so we remove it
        // with rimraf instead of calling cleanup()
        rimrafSync(cachePath);
      });

      it("caches the model the first time getSemanticModel is called", async () => {
        const ui5Model = await getSemanticModel(cachePath);
        assertSemanticModel(ui5Model);

        // Check the files were created in the folder
        const files = await readdir(cachePath);
        expect(files).to.not.be.empty;

        // Call getSemanticModel again with the same path and check it doesn't try to read from the URL
        let fetcherCalled = false;
        const ui5ModelFromCache = await getSemanticModelWithFetcher(
          (url: string): never => {
            fetcherCalled = true;
            throw new Error(
              `The files should be taken from the cache, got call for ${url}`
            );
          },
          cachePath
        );
        expect(fetcherCalled).to.be.false;
        // Make sure it's not the model itself that is cached
        expect(ui5ModelFromCache).to.not.equal(ui5Model);
        // Check we got the same result (we can't use deep equal so the check is shallow)
        forEach(ui5Model, (value, key) => {
          if (isPlainObject(value)) {
            expect(
              Object.keys(
                ui5ModelFromCache[key as keyof UI5SemanticModel] as Record<
                  string,
                  unknown
                >
              )
            ).to.deep.equalInAnyOrder(
              Object.keys(value as Record<string, unknown>)
            );
          }
        });
        assertSemanticModel(ui5ModelFromCache);
      }).timeout(GET_MODEL_TIMEOUT);

      it("doesn't fail when file cannot be written to the cache", async () => {
        // Create a folder with the file name so the file will not be written
        const cacheFilePath = getCacheFilePath(
          getCacheFolder(cachePath, VERSION),
          "sap.m"
        );
        expectExists(cacheFilePath, "cacheFilePath");
        await mkdirs(cacheFilePath);

        const ui5Model = await getSemanticModel(cachePath);
        expect(ui5Model).to.exist;
        // Check we still got the sap.m library data
        expect(Object.keys(ui5Model.namespaces)).to.contain("sap.m");
        expect(ui5Model.namespaces["sap.m"].library).to.equal("sap.m");
      }).timeout(GET_MODEL_TIMEOUT);

      it("doesn't fail when file cannot be read from the cache", async () => {
        // Create a file with non-json content so the file will not be deserialized
        const cacheFolder = getCacheFolder(cachePath, VERSION);
        await mkdirs(cacheFolder);
        const cacheFilePath = getCacheFilePath(cacheFolder, "sap.m");
        expectExists(cacheFilePath, "cacheFilePath");
        await writeFile(cacheFilePath, "not json");

        const ui5Model = await getSemanticModel(cachePath);
        expect(ui5Model).to.exist;
        // Check we still got the sap.m library data
        expect(Object.keys(ui5Model.namespaces)).to.contain("sap.m");
        expect(ui5Model.namespaces["sap.m"].library).to.equal("sap.m");
      }).timeout(GET_MODEL_TIMEOUT);
    });

    describe("cache path is a file", async () => {
      let cachePath: string;
      let cleanup: () => Promise<void>;
      beforeEach(async () => {
        ({ path: cachePath, cleanup } = await tempFile());
      });

      afterEach(async () => {
        await cleanup();
      });

      it("does not cache the model", async () => {
        const ui5Model = await getSemanticModel(cachePath);
        assertSemanticModel(ui5Model);

        // Call getSemanticModel again with the same path and check it doesn't try to read from the URL
        let fetcherCalled = false;
        await getSemanticModelWithFetcher(async (): Promise<FetchResponse> => {
          fetcherCalled = true;
          return {
            ok: true,
            json: async (): Promise<unknown> => {
              return {};
            },
          };
        }, cachePath);
        expect(fetcherCalled).to.be.true;
      }).timeout(GET_MODEL_TIMEOUT);
    });
  });
});
Example #26
Source File: unit-spec.ts    From ui5-language-assistant with Apache License 2.0 4 votes vote down vote up
context("The ui5-language-assistant semantic model package unit tests", () => {
  describe("resolveType", () => {
    it("returns the same type if it's resolved", () => {
      const model = buildUI5Model({});
      const stringPrimitiveType: UI5Type = {
        kind: "PrimitiveType",
        name: "String",
      };
      expect(
        resolveType({
          model,
          type: stringPrimitiveType,
          typeNameFix: {},
          strict: true,
        })
      ).to.equal(stringPrimitiveType);
    });
  });

  describe("setParent", () => {
    it("fails when parent is not found in the model", () => {
      const model = buildUI5Model({});
      const classFqn = "sap.MyClass";
      const classs = buildUI5Class({
        name: "MyClass",
      });
      model.classes[classFqn] = classs;

      expect(() => {
        setParent(model, classFqn, classs);
      }).to.throw("Symbol sap not found");
    });
  });

  describe("getSymbolMaps", () => {
    it("returns all object properties on the model", () => {
      const model = buildUI5Model({});
      const objectsOnModel: unknown[] = [];

      // Get all plain objects in the model and check that getSymbolMaps returns them
      // This is done to ensure we didn't forget any symbol maps. If a map/object is added that should
      // be ignored in getSymbolMaps this loop should be updated to ignore it.
      forEach(model, (property) => {
        if (isPlainObject(property)) {
          objectsOnModel.push(property);
        }
      });

      expect(getSymbolMaps(model)).to.contain.members(objectsOnModel);
    });
  });

  describe("generate", () => {
    function generateSymbol(symbol: SymbolBase): UI5SemanticModel {
      return generate({
        version: "1.74.0",
        strict: true,
        typeNameFix: {},
        libraries: {
          testLib: {
            "$schema-ref":
              "http://schemas.sap.com/sapui5/designtime/api.json/1.0",
            symbols: [symbol],
          },
        },
      });
    }

    function generateAndVerifyClass(
      partialClass: Partial<ClassSymbol>
    ): UI5Class {
      const symbol = {
        kind: "class",
        basename: "rootSymbol",
        name: "rootSymbol",
        ...partialClass,
      } as ClassSymbol; // Casting is required because typescript compiler expects constructor to be a function
      const result = generateSymbol(symbol);
      expect(keys(result.classes), "classes").to.contain("rootSymbol");
      return result.classes["rootSymbol"];
    }

    it("sets public visibility on constructor if visibility is not defined", () => {
      const result = generateAndVerifyClass({
        constructor: {
          description: "constructor with undefined visibility",
        },
      } as Partial<ClassSymbol>); // Casting is necessary because typescript compiler expects constructor to be a function
      expectExists(result.ctor, "constructor");
      expect(result.ctor.visibility, "constructor visibility").to.equal(
        "public"
      );
    });

    it("sets public visibility on event if visibility is not defined", () => {
      const result = generateAndVerifyClass({
        events: [
          {
            name: "name",
          },
        ],
      });
      expect(result.events, "rootSymbol events").to.have.lengthOf(1);
      expect(result.events[0].visibility, "event visibility").to.equal(
        "public"
      );
    });

    it("doesn't set type when not defined on class property", () => {
      const result = generateAndVerifyClass({
        "ui5-metadata": {
          properties: [
            {
              name: "name",
            },
          ],
        },
      });
      expect(result.properties, "rootSymbol properties").to.have.lengthOf(1);
      expect(result.properties[0].type, "property type").to.be.undefined;
    });

    it("doesn't set type when not defined on class aggregation", () => {
      const result = generateAndVerifyClass({
        "ui5-metadata": {
          aggregations: [
            {
              name: "name",
            },
          ],
        },
      });
      expect(result.aggregations, "rootSymbol aggregations").to.have.lengthOf(
        1
      );
      expect(result.aggregations[0].type, "aggregation type").to.be.undefined;
    });

    it("sets cardinality to 0..n when not defined on class aggregation", () => {
      const result = generateAndVerifyClass({
        "ui5-metadata": {
          aggregations: [
            {
              name: "name",
            },
          ],
        },
      });
      expect(result.aggregations, "rootSymbol aggregations").to.have.lengthOf(
        1
      );
      expect(
        result.aggregations[0].cardinality,
        "aggregation cardinality"
      ).to.equal("0..n");
    });

    it("doesn't set type when not defined on class association", () => {
      const result = generateAndVerifyClass({
        "ui5-metadata": {
          associations: [
            {
              name: "name",
            },
          ],
        },
      });
      expect(result.associations, "rootSymbol associations").to.have.lengthOf(
        1
      );
      expect(result.associations[0].type, "association type").to.be.undefined;
    });

    it("sets cardinality to 0..1 when not defined on class association", () => {
      const result = generateAndVerifyClass({
        "ui5-metadata": {
          associations: [
            {
              name: "name",
            },
          ],
        },
      });
      expect(result.associations, "rootSymbol associations").to.have.lengthOf(
        1
      );
      expect(
        result.associations[0].cardinality,
        "association cardinality"
      ).to.equal("0..1");
    });

    it("doesn't set type when not defined on namespace field", () => {
      const symbol: NamespaceSymbol = {
        kind: "namespace",
        basename: "rootNamespace",
        name: "rootNamespace",
        properties: [
          {
            name: "myField",
          },
        ],
      };
      const result = generateSymbol(symbol);
      expect(keys(result.namespaces), "namespaces").to.contain("rootNamespace");
      const namespace = result.namespaces["rootNamespace"];
      expect(namespace.fields, "rootNamespace fields").to.have.lengthOf(1);
      expect(namespace.fields[0].type, "field type").to.be.undefined;
    });

    describe("symbols is undefined", () => {
      const fileContent = {
        "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0",
        version: "1.74.0",
        library: "testLib",
      };

      it("doesn't fail in strict mode", () => {
        const model = generate({
          version: fileContent.version,
          libraries: { testLib: fileContent },
          typeNameFix: {},
          strict: true,
        });
        expectExists(model, "model");
      });

      it("doesn't fail in non-strict mode", () => {
        const model = generate({
          version: fileContent.version,
          libraries: { testLib: fileContent },
          typeNameFix: {},
          strict: false,
        });
        expectExists(model, "model");
      });
    });
  });

  context("includedLibraries", () => {
    function generateFromLibraries(
      libraries: Record<string, unknown>
    ): UI5SemanticModel {
      const result = generate({
        version: "1.74.0",
        strict: false,
        typeNameFix: {},
        libraries: libraries,
        printValidationErrors: false,
      });
      return result;
    }

    const libWithSymbol = {
      "$schema-ref": "http://schemas.sap.com/sapui5/designtime/api.json/1.0",
      symbols: [
        {
          kind: "class",
          basename: "rootSymbol",
          name: "rootSymbol",
        },
      ],
    };

    it("contains a valid library", () => {
      const result = generateFromLibraries({
        testLib: libWithSymbol,
      });
      expect(result.includedLibraries, "includedLibraries").to.deep.equal([
        "testLib",
      ]);
    });

    it("contains an invalid library object", () => {
      const result = generateFromLibraries({
        testLib: {},
      });
      expect(result.includedLibraries, "includedLibraries").to.deep.equal([
        "testLib",
      ]);
    });

    it("contains an empty library", () => {
      const result = generateFromLibraries({
        testLib: "",
      });
      expect(result.includedLibraries, "includedLibraries").to.deep.equal([
        "testLib",
      ]);
    });

    it("contains all sent libraries", () => {
      const result = generateFromLibraries({
        testLib: libWithSymbol,
        lib2: libWithSymbol,
        emptyLib: {},
      });
      expect(
        result.includedLibraries,
        "includedLibraries"
      ).to.deep.equalInAnyOrder(["testLib", "lib2", "emptyLib"]);
    });
  });

  context("API JSON fixes", () => {
    context("addViewDefaultAggregation", () => {
      it("doesn't fail when there is a sap.ui.core.mvc.View with no ui5-metadata", () => {
        addViewDefaultAggregation("sap.ui.core", {
          symbols: [
            {
              name: "sap.ui.core.mvc.View",
            },
          ],
        });
      });
    });
  });
});
Example #27
Source File: index.tsx    From gant-design with MIT License 4 votes vote down vote up
withSelector = compose(
  defaultProps(defaultprop),
  withState('label', 'setLabel', null), // 读模式下的显示文本
  withState('cacheLabel', 'setCacheLabel', ({ optionLabel }) => optionLabel), // 当前选项的文本, 在点确认的时候才更新
  withState('loading', 'setLoading', false),
  withState('filter', 'setFilter', ''),
  withState('selectRef', 'setSelectRef', null), // select组件
  withState('dataList', 'setDataList', ({ dataSource }) => dataSource),

  // 监听搜索
  withPropsOnChange(['filter'], ({ filter, selectorId }) => ({
    taskId: `${selectorId}:${escape(filter).replace(/\%u/g, '')}`,
  })),
  withHandlers({
    //将最近选择的项的key转化为真实的key
    storageToReal: ({ selectorId, reg }) => value => {
      // 最近选择
      if (value?.startsWith(selectorId)) return value.replace(reg, '$1');
      return value;
    },
  }),
  withHandlers({
    getValue: ({ valueProp }) => data =>
      String(valueProp && isPlainObject(data) ? data[valueProp] : data), // 获取选项的value
    getLabel: ({ storageToReal, valueProp, labelProp }) => data => {
      if (labelProp && isPlainObject(data))
        return valueProp == labelProp ? storageToReal(data[labelProp]) : data[labelProp];
      return data;
    }, // 获取选项的label
    setLabel: ({ setLabel: originSetLabel, splitStr = '、' }) => labels =>
      originSetLabel(Array.isArray(labels) ? labels.filter(Boolean).join(splitStr) : labels), // 重置setlabel方法,增加格式化的功能
  }),
  withHandlers({
    // 从dataList或者storageList中找到数据
    getItemLabel: ({
      dataList,
      storageList,
      selectorId,
      getValue,
      storageToReal,
      getLabel,
      optionLabel,
      useStorage,
    }) => (value, index = 0) => {
      let list = concat(dataList, storageList);
      // 启用缓存的情况下执行判断
      // fix: 解决当storageId恰好是value的前缀的情况
      if (useStorage && value?.startsWith(selectorId)) {
        list = storageList;
      }
      const valueItem = list.find(item => storageToReal(getValue(item)) === value);
      if (valueItem) return getLabel(valueItem);
      const optionLabelArray = Array.isArray(optionLabel) ? optionLabel : [optionLabel];
      return optionLabelArray[index];
    },
  }),
  withPropsOnChange(['multiple', 'mode'], ({ multiple, mode }) => ({
    isMultiple: multiple || mode === 'multiple' || mode === 'tags',
  })),
  withPropsOnChange(
    ['value'],
    ({ dataList, storageList, value, getValue, selectorId, isMultiple }) => {
      if (isNil(value)) {
        return {
          value: undefined,
        };
      }
      const isArray = Array.isArray(value);
      let cValue = isArray ? value : [value];
      const transormedValue = cValue.map(cv => {
        const v = String(cv);
        const isInList = dataList.find(item => getValue(item) === v);
        const isInStorage = storageList.find(item => getValue(item) === v);
        // 选择的缓存中的数据,需要做一层转化
        if (!isInList && isInStorage) {
          return `${selectorId}-${v}`;
        }
        return v;
      });
      return {
        value: isMultiple ? transormedValue : transormedValue[0],
      };
    },
  ),
  withHandlers({
    // 依赖转化后的value
    transformDataToList: ({
      getLabel,
      getValue,
      renderItem,
      optionLabelProp,
      hideSelected,
      isMultiple,
      value: comValue,
    }) => list =>
      list.map(item => {
        const transformItemInfo = item => {
          const value = getValue(item);
          const key = value || item.key;
          const label = getLabel(item);
          return { value, key, label };
        };

        if (renderItem) {
          return renderItem(
            isPlainObject(item) ? { ...item, ...transformItemInfo(item) } : item,
            Option,
          );
        }

        if (isPlainObject(item)) {
          const { disabled, title, className } = item;
          const { value, key, label } = transformItemInfo(item);
          let show = true,
            style;
          if (hideSelected) {
            if (isMultiple) {
              show = comValue.every(v => v !== value);
            } else {
              show = comValue !== value;
            }
          }
          if (!show) style = { display: 'none' };
          //支持 antd提供的回填到选择框的 Option 的属性值参数功能
          const optionLabelPropObj =
            optionLabelProp && item[optionLabelProp]
              ? { [optionLabelProp]: item[optionLabelProp] }
              : {};
          return (
            <Option
              key={key}
              value={value}
              disabled={disabled}
              title={title}
              style={style}
              className={className}
              {...optionLabelPropObj}
            >
              {label}
            </Option>
          );
        }
        return (
          <Option key={item} value={item}>
            {item}
          </Option>
        );
      }),
    setLabelWithValue: ({ value, setLabel, setCacheLabel, getItemLabel }) => () => {
      if (isNil(value)) {
        setLabel(null);
        return;
      }
      let label = null;
      // 从dataList找到value对应的项
      // 如果没有找到就从storagelist里面找
      // 如果还是没有找到,那么就要使用optionLabel参数
      if (Array.isArray(value)) {
        // 多选
        label = value.map((itemValue, index) =>
          itemValue ? getItemLabel(itemValue, index) : null,
        );
      } else {
        label = getItemLabel(value);
      }

      setLabel(label); // 设置读模式下的显示文本
      setCacheLabel(label); // 设置选项的label
    },
  }),
  withHandlers({
    updateStorage: ({
      selectorId,
      selectorStorageId,
      storageList,
      getValue,
      valueProp,
      setStorageList,
      useStorage,
      historyLength,
    }) => (data, update) => {
      if (!useStorage) return; // 不启用缓存
      let copyList = cloneDeep(storageList);
      data.map(item => {
        const id = `${selectorId}-${getValue(item)}`;
        let isUpdate = update; // 为true表示从最近选择的项里面选择,只更新
        if (!isUpdate) {
          //
          const existed = copyList.some(pItem => getValue(pItem) === id);
          isUpdate = existed; // 如果最近选择种已存在,将直接更新数据
          if (!existed) {
            // 新增最近被选择的数据
            if (valueProp && isPlainObject(item)) {
              copyList.unshift({ ...item, [valueProp]: id });
            } else {
              copyList.unshift(id);
            }
            copyList = copyList.slice(0, historyLength); // 保留最近?条
          }
        }
        if (isUpdate) {
          copyList.map(item => {
            if (getValue(item) === id) {
              // 找到被选择的那一条,更新数据
              return valueProp && isPlainObject(item) ? { ...item, [valueProp]: id } : id;
            }
            return item;
          });
        }
      });
      setStorageList(copyList); // 更新list
      localStorage.setItem(selectorStorageId, JSON.stringify(copyList)); // 更新缓存
    },
    cleanStorage: ({
      selectorId,
      selectorStorageId,
      storageList,
      getValue,
      valueProp,
      setStorageList,
      useStorage,
    }) => (data, update) => {
      setStorageList([]); // 更新list
      localStorage.setItem(selectorStorageId, JSON.stringify([])); // 更新缓存
    },
    getData: ({
      taskId,
      useCache,
      loading,
      setLoading,
      query,
      afterQuery,
      filter,
      setDataList,
    }) => () => {
      if (!query) return;
      let task = null;

      if (!useCache) {
        // 不使用选择器缓存,由业务自己决定缓存
        setLoading(true);
        task = query(filter);
      } else {
        task = selectorCache.get(taskId);
        if (!task) {
          if (loading) return;
          setLoading(true);
          task = query(filter);
          selectorCache.set(taskId, task);
        }
      }

      if (!(task.then && typeof task.then === 'function')) task = Promise.resolve(task);
      task.then(data => {
        let list = Array.isArray(data) ? data : [];
        setLoading(false);
        setDataList(list);
        afterQuery && afterQuery(list, setDataList);
        // else {
        //   throw new Error('选择器选项列表只能是数组格式')
        // }
      });
    },
  }),
  // 更新选项列表
  //#region
  withPropsOnChange(
    ['dataList', 'filter', 'storageList', 'loading'],
    ({
      dataList,
      filter,
      storageList,
      cleanStorage,
      transformDataToList,
      loading,
      useStorage,
      query,
      labelProp,
      getLabel,
      isFilter,
      customNotDataContent
    }) => {
      let result = dataList;
      if (!query && filter && isFilter) {
        /**
         * 筛选算法
         * axbxcx ---> abc true
         * abcabc ---> abc true
         * bacbcc ---> abc true
         * bbabdd ---> abc false 没有c
         */

        try {
          result = dataList.filter(item => {
            const label = getLabel(item);
            if (!label) {
              throw new Error(
                `应用选择器的过滤功能,请确保列表数据中${labelProp}属性存在,或修改'labelProp'对应的属性名称,作为过滤的依据`,
              );
            }
            return label.toLowerCase().indexOf(filter.toLowerCase()) > -1;
            // const LastIndex = filter.split('').reduce(
            //   (index, char) => {
            //     if (index === -1) return -1;
            //     let label = getLabel(item)
            //     if (!label) {
            //       throw new Error(`应用选择器的过滤功能,请确保列表数据中${labelProp}属性存在,或修改'labelProp'对应的属性名称,作为过滤的依据`)
            //     }
            //     label = label.toUpperCase()
            //     char = char.toUpperCase()
            //     return label.slice(index).indexOf(char)
            //   },
            //   0
            // )
            // return ~LastIndex
          });
        } catch (e) {
          console.error(e);
        }
      }
      let list = [
        //'加载中...' : '没有查询到数据'
        <Select.Option key="none" disabled>
          <Receiver>{locale => <>{loading ? locale.loading : customNotDataContent||locale.noData}</>}</Receiver>
        </Select.Option>,
      ];
      if (result.length) {
        const hasGroup = result.some(item => item.group);
        if (!hasGroup) {
          list = transformDataToList(result);
        } else {
          const everyGroup = result.every(item => item.group);
          const group = groupBy(result, 'group');
          // 某些项没有写group
          if (!everyGroup) {
            group['其他选项'] = group['undefined'];
          }

          list = Object.entries(group).reduce((result, [key, data]) => {
            if (key !== 'undefined') {
              result.push(
                <Select.OptGroup key={key} label={key}>
                  {transformDataToList(data)}
                </Select.OptGroup>,
              );
            }
            return result;
          }, [] as React.ReactElement[]);
        }
      }

      if (useStorage && !filter) {
        // 搜索结果
        const newItems = (
          <Select.OptGroup
            key="result"
            label={<Receiver>{locale => <>{locale.searchResult}</>}</Receiver>}
          >
            {list}
          </Select.OptGroup>
        );

        const selectedItems = (
          <Select.OptGroup
            key="recent"
            label={
              <Receiver>
                {locale => (
                  <div style={{ width: '100%', display: 'flex' }}>
                    <span style={{ flex: 1 }}>{locale.recentSelect}</span>
                    <Tooltip title={locale.clearRecent}>
                      <Icon
                        type="delete"
                        style={{
                          fontSize: '12px',
                          lineHeight: '32px',
                        }}
                        onClick={() => {
                          cleanStorage();
                        }}
                      />
                    </Tooltip>
                  </div>
                )}
              </Receiver>
            }
          >
            {storageList.length ? (
              transformDataToList(storageList)
            ) : (
              <Select.Option key="empty" disabled>
                <Receiver>{locale => <>{locale.noRecent}</>}</Receiver>
              </Select.Option>
            )}
          </Select.OptGroup>
        );
        return {
          renderList: [selectedItems].concat(newItems),
        };
      } else {
        return {
          renderList: list,
        };
      }
    },
  ),
  //#endregion
  withPropsOnChange(['query'], ({ getData }) => getData()),
  // 下列属性变化的时候重新根据value值设置label
  withPropsOnChange(['value', 'optionLabel', 'dataList'], ({ setLabelWithValue }) =>
    setLabelWithValue(),
  ),
  // 监听label
  // withPropsOnChange(['optionLabel'], ({ optionLabel }) => {
  //   return {
  //     cacheLabel: optionLabel
  //   }
  // }),
  // 去支持只传递dataSource,并且希望更新dataSource的情况
  withPropsOnChange(['dataSource'], ({ dataSource, setDataList }) => setDataList(dataSource)),
  mapProps(({ dataSource, transformDataToList, ...props }) => props),
)
Example #28
Source File: test-env-detail.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
TestEnvDetail = (props: IProps) => {
  const { data, disabled, visible, onCancel, envID, envType, testType } = props;
  const [{ headerMode, globalMode, headerJsonValid, globalJsonValid }, updater, update] = useUpdate({
    headerMode: 'form',
    globalMode: 'form',
    headerJsonValid: true,
    globalJsonValid: true,
  });

  React.useEffect(() => {
    if (!visible) {
      update({
        headerMode: 'form',
        globalMode: 'form',
        headerJsonValid: true,
        globalJsonValid: true,
      });
    }
  }, [update, visible]);

  const formRef = React.useRef({}) as React.MutableRefObject<FormInstance>;
  const HeaderKeyComp = ({ record, update: _update, ...rest }: any) => {
    return <InputSelect options={headerListOption} value={record.key} disabled={disabled} onChange={_update} />;
  };

  const GlobalKeyComp = ({ record, update: _update, ...rest }: any) => (
    <Input value={record.key} onChange={(e) => _update(e.target.value.trim())} maxLength={500} {...rest} />
  );

  const GlobalDescComp = ({ record, update: _update, ...rest }: any) => (
    <Select value={record.type || typeMap[testType].string} allowClear onChange={_update} {...rest}>
      {map(typeMap[testType], (value, key) => (
        <Option key={key} value={value}>
          {value}
        </Option>
      ))}
    </Select>
  );

  const KeyDescComp = ({ record, keyDesc, update: _update, ...rest }: any) => (
    <Input value={record[keyDesc]} onChange={(e) => _update(e.target.value)} {...rest} />
  );

  const ValueComp = ({ record, valueName, update: _update, ...rest }: any) => (
    <Input value={record[valueName]} onChange={(e) => _update(e.target.value)} {...rest} />
  );

  const getFieldsList = (_type: string, _headMode: string, _globalMode: string) => {
    const headFieldsStatus = _headMode === 'form' ? ['', 'hidden'] : ['hidden', ''];
    const globalFieldsStatus = _globalMode === 'form' ? ['', 'hidden'] : ['hidden', ''];

    const fieldMap = {
      auto: [
        {
          label: i18n.t('Name'),
          name: 'displayName',
          itemProps: {
            maxLength: 191,
            disabled,
          },
        },
        {
          label: i18n.t('Description'),
          name: 'desc',
          type: 'textArea',
          itemProps: {
            maxLength: 512,
            disabled,
          },
        },
      ],
      manual: [
        {
          label: i18n.t('dop:Environment name'),
          name: 'name',
          itemProps: {
            maxLength: 50,
            disabled,
          },
        },
      ],
    };

    return [
      ...fieldMap[_type],
      {
        label: i18n.t('dop:Environmental domain'),
        name: 'domain',
        getComp: () => <ProtocolInput disabled={disabled} />,
        required: false,
      },
      {
        getComp: () => (
          <div className="flex justify-between items-center">
            <div>
              <span className="font-bold">Header</span>
            </div>
            <Radio.Group
              value={headerMode}
              onChange={(e: RadioChangeEvent) => {
                const _mode = e.target.value;
                const curForm = formRef.current;
                const curFormData = curForm.getFieldsValue();
                if (_mode === 'form') {
                  formRef.current.setFieldsValue({
                    header: JSON.parse(curFormData.headerStr),
                  });
                } else {
                  const isObj = isPlainObject(curFormData.header);
                  formRef.current.setFieldsValue({
                    headerStr: JSON.stringify(
                      filter(
                        map(curFormData.header, (item, k) => {
                          let reItem = {};
                          if (isObj) {
                            reItem = { value: item, key: k };
                          } else {
                            reItem = { key: item.key, value: item.value };
                          }
                          return reItem;
                        }),
                        (item) => item.key,
                      ),
                      null,
                      2,
                    ),
                  });
                }
                updater.headerMode(e.target.value);
              }}
            >
              <Radio.Button disabled={!headerJsonValid} value="form">
                {disabled ? firstCharToUpper(i18n.t('common:form')) : i18n.t('common:Form Edit')}
              </Radio.Button>
              <Radio.Button value="code">
                {disabled ? firstCharToUpper(i18n.t('common:text')) : i18n.t('common:Text Edit')}
              </Radio.Button>
            </Radio.Group>
          </div>
        ),
        extraProps: {
          className: 'mb-2',
        },
      },
      {
        name: 'header',
        required: false,
        getComp: () => <KVPairTable disabled={disabled} KeyComp={HeaderKeyComp} ValueComp={ValueComp} />,
        itemProps: {
          type: headFieldsStatus[0],
        },
      },
      {
        name: 'headerStr',
        required: false,
        getComp: () => <JsonFileEditor readOnly={disabled} />,
        itemProps: {
          type: headFieldsStatus[1],
        },
      },
      {
        getComp: () => (
          <div className="flex justify-between items-center">
            <span className="font-bold">Global</span>
            <Radio.Group
              value={globalMode}
              onChange={(e: RadioChangeEvent) => {
                const _mode = e.target.value;
                const curForm = formRef.current;
                const curFormData = curForm.getFieldsValue();
                if (_mode === 'form') {
                  formRef.current.setFieldsValue({
                    global: JSON.parse(curFormData.globalStr),
                  });
                } else {
                  const isObj = isPlainObject(curFormData.global);
                  formRef.current.setFieldsValue({
                    globalStr: JSON.stringify(
                      filter(
                        map(curFormData.global, (item, k) => {
                          const { desc = '', value = '', type = '' } = item;
                          const reItem = { desc, value, type, key: isObj ? k : item.key };
                          if (!reItem.type) reItem.type = typeMap.auto.string;
                          return reItem;
                        }),
                        (item) => item.key,
                      ),
                      null,
                      2,
                    ),
                  });
                }
                updater.globalMode(e.target.value);
              }}
            >
              <Radio.Button disabled={!globalJsonValid} value="form">
                {disabled ? firstCharToUpper(i18n.t('common:form')) : i18n.t('common:Form Edit')}
              </Radio.Button>
              <Radio.Button value="code">
                {disabled ? firstCharToUpper(i18n.t('common:text')) : i18n.t('common:Text Edit')}
              </Radio.Button>
            </Radio.Group>
          </div>
        ),
        extraProps: {
          className: 'mb-2',
        },
      },
      {
        name: 'global',
        required: false,
        getComp: () => (
          <KVPairTable
            disabled={disabled}
            KeyComp={GlobalKeyComp}
            DescComp={GlobalDescComp}
            descName="type"
            KeyDescComp={KeyDescComp}
            keyDesc="desc"
          />
        ),
        itemProps: {
          type: globalFieldsStatus[0],
        },
      },
      {
        name: 'globalStr',
        required: false,
        getComp: () => <JsonFileEditor readOnly={disabled} />,
        itemProps: {
          type: globalFieldsStatus[1],
        },
      },
    ];
  };

  const fieldsList = getFieldsList(testType, headerMode, globalMode);

  const onUpdateHandle = React.useCallback(
    (values, header, global) => {
      if (testType === 'manual') {
        testEnvStore.updateTestEnv({ ...values, id: data.id, header, global, envType, envID }, { envType, envID });
      } else {
        testEnvStore.updateAutoTestEnv({
          apiConfig: {
            domain: values.domain,
            name: values.name,
            header,
            global,
          },
          scope: scopeMap.autoTest.scope,
          scopeID: String(envID),
          ns: data.ns,
          displayName: values.displayName,
          desc: values.desc,
        });
      }
    },
    [data, envID, envType, testType],
  );

  const onCreateHandle = React.useCallback(
    (values, header, global) => {
      if (testType === 'manual') {
        testEnvStore.createTestEnv({ ...values, header, global, envType, envID }, { envType, envID });
      } else {
        testEnvStore.createAutoTestEnv(
          {
            apiConfig: {
              ...values,
              header,
              global,
            },
            scope: scopeMap.autoTest.scope,
            scopeID: String(envID),
            displayName: values.displayName,
            desc: values.desc,
          },
          { scope: scopeMap.autoTest.scope, scopeID: envID },
        );
      }
    },
    [envID, envType, testType],
  );

  const handleSubmit = (values: any) => {
    if (!globalJsonValid || !headerJsonValid) {
      return Promise.reject();
    }
    if (disabled) {
      onCancel();
      return;
    }
    const { headerStr, globalStr, ..._rest } = values;
    const curHeader = headerMode === 'form' ? values.header : JSON.parse(headerStr || '[]');
    const curGlobal = globalMode === 'form' ? values.global : JSON.parse(globalStr || '[]');
    const header = transHeader(curHeader);
    const global = transGlobal(curGlobal);

    if (!isEmpty(data)) {
      onUpdateHandle(_rest, header, global);
    } else {
      onCreateHandle(_rest, header, global);
    }
    onCancel();
  };

  const onValuesChange = React.useCallback(
    debounce((_formRef: { form: FormInstance }, changeValues: Obj, allValues: Obj) => {
      if (changeValues.headerStr) {
        const curHeaderValid = isValidJsonStr(changeValues.headerStr);
        updater.headerJsonValid(curHeaderValid);
      }
      if (changeValues.globalStr) {
        const curGlobalValid = isValidJsonStr(changeValues.globalStr);
        updater.globalJsonValid(curGlobalValid);
      }
    }, 300),
    [],
  );

  return (
    <FormModal
      name={i18n.t('dop:parameter configuration')}
      visible={visible}
      width={900}
      modalProps={{
        destroyOnClose: true,
      }}
      formOption={{ onValuesChange }}
      formData={data}
      ref={formRef}
      fieldsList={fieldsList}
      onOk={handleSubmit}
      onCancel={onCancel}
      formProps={{ layout: 'vertical' }}
    />
  );
}
Example #29
Source File: tpl-builtin.ts    From brick-design with MIT License 4 votes vote down vote up
export function dataMapping(
  to: any,
  from: PlainObject,
  ignoreFunction: boolean | ((key: string, value: any) => boolean) = false,
): any {
  let ret = {};

  if (Array.isArray(to)) {
    return to.map((item) => dataMapping(item, from, ignoreFunction));
  } else if (!to) {
    return ret;
  }

  Object.keys(to).forEach((key) => {
    const value = to[key];
    let keys: Array<string>;

    if (typeof ignoreFunction === 'function' && ignoreFunction(key, value)) {
      // 如果被ignore,不做数据映射处理。
      (ret as PlainObject)[key] = value;
    } else if (key === '&' && value === '$$') {
      ret = {
        ...ret,
        ...from,
      };
    } else if (key.includes('$') && typeof value === 'string') {
        ret[key.substring(1)] = createStr2Function(value, from);
    } else if (key === '&') {
      const v =
        isPlainObject(value) &&
        (keys = Object.keys(value)) &&
        keys.length === 1 &&
        from[keys[0].substring(1)] &&
        Array.isArray(from[keys[0].substring(1)])
          ? from[keys[0].substring(1)].map((raw: object) =>
              dataMapping(
                value[keys[0]],
                createObject(from, raw),
                ignoreFunction,
              ),
            )
          : resolveMapping(value, from);

      if (Array.isArray(v) || typeof v === 'string') {
        ret = v;
      } else if (typeof v === 'function') {
        ret = {
          ...ret,
          ...v(from),
        };
      } else {
        ret = {
          ...ret,
          ...v,
        };
      }
    } else if (value === '$$') {
      ret[key] = from;
    } else if (value && value[0] === '$') {
      const v = resolveMapping(value, from);
      ret[key] = v;

      if (v === '__undefined') {
        delete (ret as PlainObject)[key];
      }
    } else if (value && value[0] === '&') {
      ret[key] = evalExpression(/\&{([^}{]+)}/g.exec(value)[1], from);
    } else if (
      isPlainObject(value) &&
      (keys = Object.keys(value)) &&
      keys.length === 1 &&
      from[keys[0].substring(1)] &&
      Array.isArray(from[keys[0].substring(1)])
    ) {
      const arr = from[keys[0].substring(1)];
      const mapping = value[keys[0]];

      ret[key] = arr.map((raw: object) =>
        dataMapping(mapping, createObject(from, raw), ignoreFunction),
      );
    } else if (isPlainObject(value)) {
      ret[key] = dataMapping(value, from, ignoreFunction);
    } else if (Array.isArray(value)) {
      ret[key] = value.map((value: any) =>
        isPlainObject(value)
          ? dataMapping(value, from, ignoreFunction)
          : resolveMapping(value, from),
      );
    } else if (typeof value == 'string' && value.includes('$')) {
      ret[key] = resolveMapping(value, from);
    } else if (typeof value === 'function' && ignoreFunction !== true) {
      ret[key] = value(from);
    } else {
      ret[key] = value;

      if (value === '__undefined') {
        delete (ret as PlainObject)[key];
      }
    }
  });

  return ret;
}